Skip to content

Commit 31cdf70

Browse files
2 parents d74889a + f2c3c19 commit 31cdf70

File tree

9 files changed

+312
-129
lines changed

9 files changed

+312
-129
lines changed

assets/img/examples/auth0-origin.mp4

15.7 MB
Binary file not shown.
13.2 MB
Binary file not shown.

content/_changelogs/12.0.0.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,13 @@ _Released MM/DD/YYYY_
1010
`on`.
1111
- Cypress now throws an error if commands are invoked from inside a `.should()`
1212
callback. This previously resulted in unusual and undefined behavior; it is
13-
now explicitly an error.
13+
now explicitly an error. <<<<<<< HEAD =======
14+
- The [`.within()`](/api/commands/within) command now throws an error if given
15+
more than one DOM element as a subject. This is done for consistency - in
16+
older versions, some commands inside a `.within()` block would respect all
17+
passed in elements, while others silently discarded subjects beyond the first
18+
and `.screenshot()` would throw an error.
19+
> > > > > > > f2c3c1944ed572fc96b8a8001c53b80a9bdb25de
1420
- `Cookies.defaults` and `Cookies.preserveOnce` have been removed. Please update
1521
to use [`cy.session()`](/api/commands/session) to preserve session details
1622
between tests. Addresses

content/api/commands/within.md

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,17 @@ chain further commands that rely on the subject after `.within()`.
2020
**<Icon name="check-circle" color="green"></Icon> Correct Usage**
2121

2222
```javascript
23-
cy.get('.list').within(($list) => {}) // Yield the `.list` and scope all commands within it
23+
cy.get('.list')
24+
.first()
25+
.within(($list) => {}) // Yield the first `.list` and scope all commands within it
2426
```
2527

2628
**<Icon name="exclamation-triangle" color="red"></Icon> Incorrect Usage**
2729

2830
```javascript
2931
cy.within(() => {}) // Errors, cannot be chained off 'cy'
3032
cy.getCookies().within(() => {}) // Errors, 'getCookies' does not yield DOM element
33+
cy.get('div').within(($divs) => {}) // Probably errors, because get('div') yields multiple elements
3134
```
3235

3336
### Arguments
@@ -179,7 +182,8 @@ cy.get('form').within(($form) => {
179182

180183
### Requirements [<Icon name="question-circle"/>](/guides/core-concepts/introduction-to-cypress#Chains-of-Commands)
181184

182-
<List><li>`.within()` requires being chained off a previous command.</li></List>
185+
- `.within()` requires being chained off a previous command that yields exactly
186+
one DOM element.
183187

184188
### Assertions [<Icon name="question-circle"/>](/guides/core-concepts/introduction-to-cypress#Assertions)
185189

@@ -211,10 +215,11 @@ outputs the following:
211215

212216
## History
213217

214-
| Version | Changes |
215-
| --------------------------------------------- | ------------------------------------------------------- |
216-
| [< 0.3.3](/guides/references/changelog#0-3-3) | `.within()` command added |
217-
| [5.4.0](/guides/references/changelog#5-4-0) | fixed the yielded value to always be the parent element |
218+
| Version | Changes |
219+
| --------------------------------------------- | -------------------------------------------------------------------------------- |
220+
| [12.0.0](/guides/references/changelog#12-0-0) | `.within()` now throws an error when given more than one element as the subject. |
221+
| [5.4.0](/guides/references/changelog#5-4-0) | fixed the yielded value to always be the parent element |
222+
| [< 0.3.3](/guides/references/changelog#0-3-3) | `.within()` command added |
218223

219224
## See also
220225

content/guides/end-to-end-testing/auth0-authentication.md

Lines changed: 156 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,15 @@ e2eSpecific: true
77

88
## <Icon name="graduation-cap"></Icon> What you'll learn
99

10+
- Log in to [Auth0](https://auth0.com) through the UI with
11+
[`cy.origin()`](/api/commands/origin)
1012
- Programmatically authenticate with [Auth0](https://auth0.com) via a custom
1113
Cypress command
12-
- Adapting your [Auth0](https://auth0.com) application for programmatic
14+
- Adapt your [Auth0](https://auth0.com) application for programmatic
1315
authentication during testing
1416

1517
</Alert>
1618

17-
<Alert type="success">
18-
19-
<strong class="alert-header">Why authenticate programmatically?</strong>
20-
21-
Typically, logging in a user within your app by authenticating via a third-party
22-
provider requires visiting login pages hosted on a different domain. Since each
23-
Cypress test is limited to visiting domains of the same origin, we can subvert
24-
visiting and testing third-party login pages by programmatically interacting
25-
with the third-party authentication API to login a user.
26-
27-
</Alert>
28-
2919
<Alert type="warning">
3020

3121
This guide is setup for testing against an [Auth0](https://auth0.com) Single
@@ -36,6 +26,22 @@ for automated end-to-end testing.
3626

3727
</Alert>
3828

29+
<Alert type="success">
30+
31+
<strong class="alert-header">Authenticate by visiting a different domain with
32+
[`cy.origin()`](/api/commands/origin)</strong>
33+
34+
Typically, logging in a user within your app by authenticating via a third-party
35+
provider requires visiting a login page hosted on a different domain. Before
36+
Cypress [v12.0.0](https://on.cypress.io/changelog#12-0-0), Cypress tests were
37+
limited to visiting domains of the same origin, making programmatic login the
38+
only option for authenticating users with a third-party API. As of Cypress
39+
[v12.0.0](https://on.cypress.io/changelog#12-0-0), Cypress tests are no longer
40+
limited to visiting domains of a single origin, meaning you can easily
41+
authenticate with [Auth0](https://auth0.com) via the UI!
42+
43+
</Alert>
44+
3945
## Auth0 Application Setup
4046

4147
To get started with Auth0, an application needs to be setup within the
@@ -111,23 +117,151 @@ require('dotenv').config()
111117
}
112118
```
113119

120+
Note that `auth0_client_secret` is only needed for
121+
[programmatic login](#Programmatic-Login).
122+
114123
:::
115124

116125
## Custom Command for Auth0 Authentication
117126

127+
There are two ways you can authenticate to Auth0:
128+
129+
- [Login with `cy.origin()`](#Login-with-cy-origin)
130+
- [Programmatic Login](#Programmatic-Login)
131+
132+
### Login with [`cy.origin()`](/api/commands/origin)
133+
134+
Next, we'll write a custom command called `loginToAuth0` to perform a login to
135+
[Auth0](https://auth0.com). This command will use
136+
[`cy.origin()`](/api/commands/origin) to
137+
138+
1. Navigate to the Auth0 login
139+
2. Input user credentials
140+
3. Sign in and redirect back to the
141+
[Cypress Real World App](https://github.com/cypress-io/cypress-realworld-app)
142+
4. Cache the results with [`cy.session()`](/api/commands/session)
143+
144+
```js
145+
// cypress/support/auth-provider-commands/auth0.ts
146+
147+
function loginViaAuth0Ui(username: string, password: string) {
148+
// App landing page redirects to Auth0.
149+
cy.visit('/')
150+
151+
// Login on Auth0.
152+
cy.origin(
153+
Cypress.env('auth0_domain'),
154+
{ args: { username, password } },
155+
({ username, password }) => {
156+
cy.get('input#username').type(username)
157+
cy.get('input#password').type(password, { log: false })
158+
cy.contains('button[value=default]', 'Continue').click()
159+
}
160+
)
161+
162+
// Ensure Auth0 has redirected us back to the RWA.
163+
cy.url().should('equal', 'http://localhost:3000/')
164+
}
165+
166+
Cypress.Commands.add('loginToAuth0', (username: string, password: string) => {
167+
const log = Cypress.log({
168+
displayName: 'AUTH0 LOGIN',
169+
message: [`🔐 Authenticating | ${username}`],
170+
// @ts-ignore
171+
autoEnd: false,
172+
})
173+
log.snapshot('before')
174+
175+
loginViaAuth0Ui(username, password)
176+
177+
log.snapshot('after')
178+
log.end()
179+
})
180+
```
181+
182+
Now, we can use our `loginToAuth0` command in the test. Below is our test to
183+
login as a user via Auth0 and run a basic sanity check.
184+
185+
<Alert type="success">
186+
187+
The
188+
[runnable version of this test](https://github.com/cypress-io/cypress-realworld-app/blob/develop/cypress/tests/ui-auth-providers/auth0.spec.ts)
189+
is in the
190+
[Cypress Real World App](https://github.com/cypress-io/cypress-realworld-app).
191+
192+
</Alert>
193+
194+
```js
195+
describe('Auth0', function () {
196+
beforeEach(function () {
197+
cy.task('db:seed')
198+
cy.intercept('POST', '/graphql').as('createBankAccount')
199+
cy.loginToAuth0(
200+
Cypress.env('auth0_username'),
201+
Cypress.env('auth0_password')
202+
)
203+
cy.visit('/')
204+
})
205+
206+
it('shows onboarding', function () {
207+
cy.contains('Get Started').should('be.visible')
208+
})
209+
})
210+
```
211+
212+
<DocsVideo src="/img/examples/auth0-origin.mp4"></DocsVideo>
213+
214+
Lastly, we can refactor our login command to take advantage of
215+
[`cy.session()`](/api/commands/session) to store our logged in user so we don't
216+
have to reauthenticate before every test.
217+
218+
```js
219+
Cypress.Commands.add('loginToAuth0', (username: string, password: string) => {
220+
const log = Cypress.log({
221+
displayName: 'AUTH0 LOGIN',
222+
message: [`🔐 Authenticating | ${username}`],
223+
// @ts-ignore
224+
autoEnd: false,
225+
})
226+
log.snapshot('before')
227+
228+
cy.session(
229+
`auth0-${username}`,
230+
() => {
231+
loginViaAuth0Ui(username, password)
232+
},
233+
{
234+
validate: () => {
235+
// Validate presence of access token in localStorage.
236+
cy.wrap(localStorage)
237+
.invoke('getItem', 'authAccessToken')
238+
.should('exist')
239+
},
240+
}
241+
)
242+
243+
log.snapshot('after')
244+
log.end()
245+
})
246+
```
247+
248+
<DocsVideo src="/img/examples/auth0-session-restore.mp4"></DocsVideo>
249+
250+
### Programmatic Login
251+
118252
Below is a command to programmatically login into [Auth0](https://auth0.com),
119253
using the
120254
[/oauth/token endpoint](https://auth0.com/docs/protocols/protocol-oauth2#token-endpoint)
121-
and set an item in localStorage with the authenticated users details, which we
255+
and set an item in `localStorage` with the authenticated users details, which we
122256
will use in our application code to verify we are authenticated under test.
123257

124258
The `loginByAuth0Api` command will execute the following steps:
125259

126260
1. Use the
127261
[/oauth/token endpoint](https://auth0.com/docs/protocols/protocol-oauth2#token-endpoint)
128262
to perform the programmatic login.
129-
2. Finally the `auth0Cypress` localStorage item is set with the `access token`,
130-
`id_token` and user profile.
263+
2. Finally the `auth0Cypress` `localStorage` item is set with the
264+
`access token`, `id_token` and user profile.
131265

132266
```jsx
133267
// cypress/support/commands.js
@@ -216,17 +350,6 @@ describe('Auth0', function () {
216350
})
217351
```
218352

219-
<Alert type="success">
220-
221-
<strong class="alert-header">Try it out</strong>
222-
223-
The
224-
[runnable version of this test](https://github.com/cypress-io/cypress-realworld-app/blob/develop/cypress/tests/ui-auth-providers/auth0.spec.ts)
225-
is in the
226-
[Cypress Real World App](https://github.com/cypress-io/cypress-realworld-app).
227-
228-
</Alert>
229-
230353
## Adapting an Auth0 App for Testing
231354

232355
<Alert type="info">
@@ -238,7 +361,11 @@ within Cypress tests. To use this practice it is assumed you are testing an app
238361
appropriately built or adapted to use Auth0.
239362

240363
The following sections provides guidance on building or adapting an app to use
241-
Auth0 authentication.
364+
Auth0 authentication. Please note that if you are
365+
[logging in with `cy.origin()`](#Login-with-cy-origin) and your app is already
366+
successfully integrated with Auth0, you do not need to make any further changes
367+
to your app and the remainder of this guide should be regarded as purely
368+
informational.
242369

243370
</Alert>
244371

@@ -420,7 +547,7 @@ if (process.env.REACT_APP_AUTH0) {
420547

421548
An update to our
422549
[AppAuth0.tsx component](https://github.com/cypress-io/cypress-realworld-app/blob/develop/src/containers/AppAuth0.tsx)
423-
is needed to conditionally use the `auth0Cypress` localStorage item.
550+
is needed to conditionally use the `auth0Cypress` `localStorage` item.
424551

425552
In the code below, we conditionally apply a `useEffect` block based on being
426553
under test with Cypress (using `window.Cypress`).

0 commit comments

Comments
 (0)