Skip to content

Commit 175a0f4

Browse files
authored
Auth0 cypress provider (#172)
* Use package.json exports to export encrypt to stop cypress global variable errors * split login based on authorization flow * lazy load auth in login handlers * lazy load auth get-user-info * move destroySimulation from logout into createSimulation * authorization flow command maker * move logout into flow specific command * typing * CYPRESS_AUTH0_FLOW to tests * refactor tests * create a destroySimulation command * remove destroy simulation from logout * increase command timeout in cypress.json * change flow to providers * update README * add changeset * names for makeLogin * remove log task * fix logout import * change AUTH0_PROVIDER to AUTH0_SDK * delete unused auth * fix cypress tests
1 parent b1412da commit 175a0f4

File tree

29 files changed

+421
-174
lines changed

29 files changed

+421
-174
lines changed

.changes/auth0-providers.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
"@simulacrum/auth0-cypress": minor
3+
---
4+
have specific cypress commands for specific auth0 javascript sdks.

.changes/use-exports-for-encrypt.md

Lines changed: 0 additions & 4 deletions
This file was deleted.

examples/nextjs/auth0-react/components/header.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,13 @@ const Header = (): React.ReactElement => {
1111
<ul>
1212
<li>
1313
<Link href="/">
14-
<a>Home</a>
14+
<a data-testid="home">Home</a>
1515
</Link>
1616
</li>
1717
{user ? (
1818
<>
1919
<li>
20-
<button
20+
<button data-testid="logout"
2121
onClick={() => logout({ returnTo: window.location.origin })}
2222
>
2323
Logout
@@ -27,7 +27,7 @@ const Header = (): React.ReactElement => {
2727
) : (
2828
<>
2929
<li>
30-
<button onClick={loginWithRedirect}>Login</button>
30+
<button onClick={loginWithRedirect} data-testid="login">Login</button>
3131
</li>
3232
</>
3333
)}

examples/nextjs/nextjs-auth0/components/header.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const Header = (): React.ReactElement => {
1111
<ul>
1212
<li>
1313
<Link href="/">
14-
<a>Home</a>
14+
<a data-testid="home">Home</a>
1515
</Link>
1616
</li>
1717
{user ? (

examples/nextjs/nextjs-auth0/pages/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export default function Home(): JSX.Element {
3333
</p>
3434
<p>
3535
Once you have logged in you should be able to click in{" "}
36-
<i>Protected Page</i> and <i>Logout</i>
36+
<i>Protected Page</i> and <i>Log out</i>
3737
</p>
3838
</>
3939
)}

integrations/cypress/README.md

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# @simulacrum/auth0-cypress
22

3+
[Cypress](https://www.cypress.io/) [Auth0](https://auth0.com) addon that simulates an auth0 server running on `localhost` so you don't have to create fake accounts while developing or running tests that require authentication.
4+
35
## Contents
46

57
- [Installation](#installation)
@@ -19,15 +21,38 @@
1921
npm install @simulacrum/auth0-cypress --dev
2022
```
2123

22-
### Step 2: Import the commands
24+
### Step 2: Choose which sdk or javascript sdk (default @auth0/auth0-react)
25+
26+
This plugin supports the following javascript sdks that interface with auth0:
27+
28+
- [@auth0/auth0-react (default)](https://github.com/auth0/auth0-react)
29+
- [nextjs-auth0] - https://github.com/auth0/nextjs-auth0
30+
31+
If you want to use `nextjs-auth0` then you need to set the `AUTH0_SDK` environment variables by any of the [usual cypress environmental varaibles options](https://docs.cypress.io/guides/guides/environment-variables).
32+
33+
e.g. in `cypress.env.json`
34+
35+
```json
36+
{
37+
"AUTH0_SDK": "nextjs-auth0",
38+
}
39+
```
40+
41+
or as a cypress env var:
42+
43+
```shell
44+
export CYPRESS_AUTH0_SDK=nextjs-auth0
45+
```
46+
47+
### Step 3: Import the commands
2348

2449
```js
2550
// cypress/support/index.js
2651

2752
import '@simulacrum/auth0-cypress';
2853
```
2954

30-
### Step 3: Register the encrypt task
55+
### Step 4: Register the encrypt task
3156

3257
We need to register an encrypt [cypress task](https://docs.cypress.io/api/commands/task).
3358

@@ -55,7 +80,7 @@ module.exports = (on, config) => {
5580
}
5681
```
5782

58-
### Step 4: Configure Auth0
83+
### Step 5: Configure Auth0
5984

6085
An example [cypress environment file](./cypress.env.json) is in the root of this repo. You can change the configuration to your auth0 values.
6186

@@ -168,21 +193,7 @@ describe('log in', () => {
168193
cy.logout();
169194
```
170195
171-
You might want to log out after every test.
172-
173-
```js
174-
afterEach(async () => {
175-
cy.logout();
176-
});
177-
```
178-
179-
You may also log out _before_ each test to allow one to inspect the state after a test run.
180-
181-
```js
182-
beforeEach(async () => {
183-
cy.logout();
184-
});
185-
```
196+
`cy.logout` will destroy the simulation and do any clean up between tests like removing an cookies.
186197
187198
## debugging
188199

integrations/cypress/cypress.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"baseUrl": "http://localhost:3000",
33
"video": false,
44
"screenshotOnRunFailure": false,
5+
"defaultCommandTimeout": 10000,
56
"fixturesFolder": "cypress/fixtures",
67
"integrationFolder": "cypress/integration",
78
"pluginsFile": "cypress/plugins/index.ts",

integrations/cypress/cypress/integration/login.spec.ts

Lines changed: 32 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,19 @@ import auth0Config from "../../cypress.env.json";
33

44
describe("auth", () => {
55
describe("log in, create person per test", () => {
6+
beforeEach(() => {
7+
cy.logout();
8+
});
9+
610
it("should log in and log out", () => {
711
cy
8-
.createSimulation(auth0Config)
912
.visit("/")
10-
.contains("Logout").should('not.exist')
13+
.createSimulation(auth0Config)
14+
.get('[data-testid=logout]').should('not.exist')
1115
.given()
1216
.login()
1317
.visit("/")
14-
.contains("Logout")
18+
.get('[data-testid=logout]').should('exist')
1519
.logout();
1620
});
1721
});
@@ -25,10 +29,11 @@ describe("auth", () => {
2529
it("should login and logout", () => {
2630
cy
2731
.visit("/")
28-
.contains("Logout").should('not.exist')
32+
.get('[data-testid=home]').should('exist')
33+
.get('[data-testid=logout]').should('not.exist')
2934
.login()
3035
.visit("/")
31-
.contains("Logout")
36+
.get('[data-testid=logout]').should('exist')
3237
.logout();
3338
});
3439
});
@@ -45,41 +50,44 @@ describe("auth", () => {
4550
it("should login once", () => {
4651
cy
4752
.visit("/")
48-
.contains("Logout").should('not.exist')
53+
.get('[data-testid=home]').should('exist')
54+
.get('[data-testid=logout]').should('not.exist')
4955
.given({ email: 'first@gmail.com' })
5056
.login()
5157
.visit("/")
52-
.contains("Logout");
58+
.get('[data-testid=logout]').should('exist');
5359
});
5460

5561
it("should login two times without error", () => {
5662
cy
5763
.visit("/")
58-
.contains("Logout").should('not.exist')
64+
.get('[data-testid=home]').should('exist')
65+
.get('[data-testid=logout]').should('not.exist')
5966
.given({ email: 'second@gmail.com' })
6067
.login()
6168
.visit("/")
62-
.contains("Logout");
69+
.get('[data-testid=logout]').should('exist');
6370
});
6471

6572
it("should login three times without error", () => {
6673
cy
67-
.visit("/")
68-
.contains("Logout").should('not.exist')
69-
.given({ email: 'third@gmail.com' })
70-
.login()
71-
.visit("/")
72-
.contains("Logout");
74+
.visit("/")
75+
.get('[data-testid=home]').should('exist')
76+
.get('[data-testid=logout]').should('not.exist')
77+
.given({ email: 'third@gmail.com' })
78+
.login()
79+
.visit("/")
80+
.get('[data-testid=logout]').should('exist');
7381
});
7482

7583
it("should login four times without error", () => {
7684
cy.visit("/")
77-
.contains("Login")
78-
.contains("Logout").should('not.exist')
85+
.get('[data-testid=home]').should('exist')
86+
.get('[data-testid=logout]').should('not.exist')
7987
.given({ email: 'fourth@gmail.com', password: 'passw0rd' })
8088
.login()
8189
.visit("/")
82-
.contains("Logout");
90+
.get('[data-testid=logout]').should('exist');
8391
});
8492
});
8593

@@ -93,20 +101,22 @@ describe("auth", () => {
93101
it("should login once", () => {
94102
cy
95103
.visit("/")
96-
.contains("Logout").should('not.exist')
104+
.get('[data-testid=home]').should('exist')
105+
.get('[data-testid=logout]').should('not.exist')
97106
.login()
98107
.visit("/")
99-
.contains("Logout");
108+
.get('[data-testid=logout]').should('exist');
100109
});
101110

102111
it("should login twice without error", () => {
103112
cy
104113
.visit("/")
105-
.contains("Logout").should('not.exist')
114+
.get('[data-testid=home]').should('exist')
115+
.get('[data-testid=logout]').should('not.exist')
106116
.given({ email: 'second@gmail.com' })
107117
.login()
108118
.visit("/")
109-
.contains("Logout");
119+
.get('[data-testid=logout]').should('exist');
110120
});
111121
});
112122
});
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import type { Auth0SDKs, CommandMaker } from '../types';
2+
import { makeCypressLogger } from '../utils/cypress-logger';
3+
import { getConfig } from '../utils/config';
4+
import { makeLogin as makeNextLogin } from './nextjs_auth0/login';
5+
import { makeLogin as makeReactLogin } from './auth0_react/login';
6+
import { makeLogout as makeNextLogout } from './nextjs_auth0/logout';
7+
import { makeLogout as makeReactLogout } from './auth0_react/logout';
8+
9+
const log = makeCypressLogger('simulacrum-provider-commands');
10+
11+
type Maker = ({ atom, getClientFromSpec }: CommandMaker) => () => void;
12+
13+
type Commands = Record<Auth0SDKs, { login: Maker, logout: Maker }>;
14+
15+
const providerCommands: Commands = {
16+
nextjs_auth0: {
17+
login: makeNextLogin,
18+
logout: makeNextLogout
19+
},
20+
auth0_react: {
21+
login: makeReactLogin,
22+
logout: makeReactLogout
23+
},
24+
} as const;
25+
26+
export const makeSDKCommands = ({ atom, getClientFromSpec }: CommandMaker) => {
27+
let config = getConfig();
28+
29+
let provider = config.sdk;
30+
31+
log(`Using ${provider} provider`);
32+
33+
let commands = providerCommands[provider];
34+
35+
for (let [name, command] of Object.entries(commands)) {
36+
Cypress.Commands.add(name, command({ atom, getClientFromSpec }));
37+
}
38+
};
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { Auth0Client, Auth0ClientOptions } from '@auth0/auth0-spa-js';
2+
3+
const Auth0ConfigDefaults: Pick<Auth0ClientOptions, 'connection' | 'scope'> = {
4+
connection: 'Username-Password-Authentication',
5+
scope: 'openid profile email',
6+
};
7+
8+
const Auth0ConfigFixed: Pick<Auth0ClientOptions, 'cacheLocation' | 'useRefreshTokens'> = {
9+
cacheLocation: 'localstorage',
10+
useRefreshTokens: true
11+
};
12+
13+
const Auth0Config: Auth0ClientOptions = {
14+
...Auth0ConfigDefaults,
15+
audience: Cypress.env('audience'),
16+
client_id: Cypress.env('clientID'),
17+
domain: Cypress.env('domain'),
18+
scope: Cypress.env('scope'),
19+
...Auth0ConfigFixed
20+
};
21+
22+
export const auth = new Auth0Client(Auth0Config);
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { CommandMaker } from '../../types';
2+
import { assert } from 'assert-ts';
3+
import { makeCypressLogger } from '../../utils/cypress-logger';
4+
5+
const log = makeCypressLogger('simulacrum-login-pkce');
6+
7+
export const makeLogin = ({ atom }: Pick<CommandMaker, 'atom'>) => () => {
8+
return new Cypress.Promise((resolve, reject) => {
9+
import('./auth').then(m => m.auth).then((auth0Client) => {
10+
let person = atom.slice(Cypress.spec.name, 'person').get();
11+
12+
assert(!!person && typeof person.email !== 'undefined', `no scenario in login`);
13+
14+
auth0Client.getTokenSilently({ ignoreCache: true, currentUser: person.email, test: Cypress.currentTest.title })
15+
.then((token) => {
16+
log(`successfully logged in with token ${JSON.stringify(token)}`);
17+
18+
resolve(token);
19+
}).catch((e) => {
20+
console.error(e);
21+
22+
reject(e);
23+
});
24+
});
25+
});
26+
};
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { makeCypressLogger } from '../../utils/cypress-logger';
2+
3+
const log = makeCypressLogger('simulacrum-logout-pkce');
4+
5+
export const makeLogout = () => () => {
6+
log('logging out');
7+
8+
return cy
9+
.clearCookies()
10+
.reload();
11+
};

0 commit comments

Comments
 (0)