Skip to content

Commit 226df14

Browse files
committed
feat: add simple e2e tests
1 parent 3cf3cc9 commit 226df14

10 files changed

Lines changed: 644 additions & 0 deletions

File tree

.github/workflows/e2e.yml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
name: Run e2e tests
2+
on:
3+
workflow_dispatch:
4+
pull_request:
5+
branches:
6+
- main
7+
8+
jobs:
9+
test:
10+
timeout-minutes: 30
11+
runs-on: ubuntu-latest
12+
steps:
13+
- name: Checkout
14+
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # ratchet:actions/checkout@v7
15+
16+
- name: Setup pnpm
17+
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # ratchet:pnpm/action-setup@v6
18+
with:
19+
package_json_file: ./e2e/package.json
20+
21+
- name: Set up Docker
22+
uses: docker/setup-docker-action@6d7cfa65f60a9dda7b46e5513fa982536f3c9877 # ratchet:docker/setup-docker-action@v5
23+
24+
- name: Install dependencies
25+
run: npm install -g pnpm && pnpm install
26+
with:
27+
working-directory: e2e
28+
29+
- name: Install Playwright Browsers
30+
run: pnpm exec playwright install --with-deps
31+
with:
32+
working-directory: e2e
33+
34+
- name: Run Playwright tests
35+
run: pnpm exec playwright test
36+
with:
37+
working-directory: e2e
38+
39+
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # ratchet:actions/upload-artifact@v4
40+
if: ${{ !cancelled() }}
41+
with:
42+
name: playwright-report
43+
path: e2e/playwright-report/
44+
retention-days: 5

e2e/.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# playwright files
2+
/node_modules/
3+
/test-results/
4+
/playwright-report/
5+
/blob-report/
6+
/playwright/.cache/
7+
/playwright/.auth/

e2e/config.e2e.yaml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
appUrl: http://tinyauth.127.0.0.1.sslip.io
2+
3+
log:
4+
level: debug
5+
6+
auth:
7+
users:
8+
# user1:password,user2:password,user3:password:token
9+
- user1:$2a$10$h1laww4k5a4bJcG5KwE3nO45YKSC4mOKHxbcccgxr3Y7H9zHlQe8e
10+
- user2:$2a$10$h1laww4k5a4bJcG5KwE3nO45YKSC4mOKHxbcccgxr3Y7H9zHlQe8e
11+
- user3:$2a$10$h1laww4k5a4bJcG5KwE3nO45YKSC4mOKHxbcccgxr3Y7H9zHlQe8e:MVR4JQWNXYKNM6HHJEYEFP2O74QIIEJE
12+
# disable rate limits for multiple workers to work
13+
loginMaxRetries: 0
14+
15+
apps:
16+
whoami:
17+
config:
18+
domain: whoami.127.0.0.1.sslip.io
19+
path:
20+
allow: /foo
21+
users:
22+
allow: user1

e2e/docker-compose.e2e.yml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
services:
2+
traefik:
3+
image: traefik:v3.6
4+
command: |
5+
--api.insecure=true
6+
--providers.docker
7+
--entrypoints.web.address=:80
8+
ports:
9+
- 80:80
10+
volumes:
11+
- /var/run/docker.sock:/var/run/docker.sock
12+
13+
whoami:
14+
image: traefik/whoami:latest
15+
labels:
16+
traefik.enable: true
17+
traefik.http.routers.whoami.rule: Host(`whoami.127.0.0.1.sslip.io`)
18+
traefik.http.routers.whoami.middlewares: tinyauth
19+
20+
tinyauth:
21+
build:
22+
context: ../
23+
dockerfile: Dockerfile
24+
args:
25+
- VERSION=e2e
26+
- BUILD_TAGS=nomsgpack
27+
- LD_FLAGS=-s -w
28+
command: ["--configfile", "/app/config.yaml"]
29+
volumes:
30+
- ./config.e2e.yaml:/app/config.yaml:ro
31+
labels:
32+
traefik.enable: true
33+
traefik.http.routers.tinyauth.rule: Host(`tinyauth.127.0.0.1.sslip.io`)
34+
traefik.http.middlewares.tinyauth.forwardauth.address: http://tinyauth:3000/api/auth/traefik

e2e/fixtures/auth.fixtures.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import {expect, Page} from '@playwright/test';
2+
import { OTP } from 'otplib';
3+
4+
export class LoginFixture {
5+
constructor(public readonly page: Page) {}
6+
7+
async run(username: string, password: string) {
8+
await expect(this.page.getByText('Welcome back, please login')).toBeVisible();
9+
await this.page.getByLabel('Username').fill(username);
10+
await this.page.getByLabel('Password').fill(password);
11+
await this.page.getByRole('button', { name: 'Login' }).click();
12+
}
13+
14+
async expectSuccess(username: string) {
15+
await expect(this.page.getByText(`You are currently logged in as ${username}.`)).toBeVisible()
16+
}
17+
}
18+
19+
export class LogoutFixture {
20+
constructor(public readonly page: Page) {}
21+
22+
async run() {
23+
await expect(this.page.getByText('Click the button below to logout.')).toBeVisible();
24+
await this.page.getByRole('button', { name: 'Logout' }).click();
25+
}
26+
27+
async expectSuccess() {
28+
await expect(this.page.getByText('Welcome back, please login')).toBeVisible();
29+
}
30+
}
31+
32+
export class TOTPFixture {
33+
constructor(public readonly page: Page) {}
34+
35+
async run(secret: string) {
36+
await expect(this.page.getByText('Enter your TOTP code')).toBeVisible();
37+
const otp = new OTP();
38+
const token = await otp.generate({ secret });
39+
await this.page.getByPlaceholder('XXXXXX').fill(token);
40+
// we shouldn't need to click continue, it will auto submit
41+
// await this.page.getByRole('button', { name: 'Continue' }).click();
42+
}
43+
44+
async expectSuccess(username: string) {
45+
await expect(this.page.getByText(`You are currently logged in as ${username}.`)).toBeVisible()
46+
}
47+
}

e2e/package.json

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"name": "e2e",
3+
"version": "1.0.0",
4+
"description": "",
5+
"main": "index.js",
6+
"scripts": {
7+
"down": "docker compose -f docker-compose.e2e.yml down",
8+
"up": "docker compose -f docker-compose.e2e.yml up --build --pull=always --force-recreate --remove-orphans",
9+
"test": "playwright test",
10+
"report": "playwright show-report"
11+
},
12+
"keywords": [],
13+
"author": "",
14+
"license": "ISC",
15+
"devEngines": {
16+
"packageManager": {
17+
"name": "pnpm",
18+
"version": "^11.1.2",
19+
"onFail": "download"
20+
}
21+
},
22+
"type": "module",
23+
"devDependencies": {
24+
"@playwright/test": "^1.61.1",
25+
"@types/node": "^26.1.1"
26+
},
27+
"dependencies": {
28+
"otplib": "^13.4.1"
29+
}
30+
}

e2e/playwright.config.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { defineConfig, devices } from '@playwright/test';
2+
3+
export default defineConfig({
4+
testDir: './specs',
5+
fullyParallel: true,
6+
forbidOnly: false,
7+
retries: 0,
8+
workers: 4,
9+
reporter: 'html',
10+
use: {
11+
trace: 'on-first-retry',
12+
video: 'on',
13+
},
14+
projects: [
15+
{
16+
name: 'chromium',
17+
use: { ...devices['Desktop Chrome'] },
18+
},
19+
{
20+
name: 'firefox',
21+
use: { ...devices['Desktop Firefox'] },
22+
},
23+
24+
{
25+
name: 'webkit',
26+
use: { ...devices['Desktop Safari'] },
27+
},
28+
{
29+
name: 'Mobile Chrome',
30+
use: { ...devices['Pixel 5'] },
31+
},
32+
{
33+
name: 'Mobile Safari',
34+
use: { ...devices['iPhone 12'] },
35+
},
36+
],
37+
webServer: {
38+
command: 'docker compose -f docker-compose.e2e.yml up --build --pull=always --force-recreate --remove-orphans',
39+
url: 'http://tinyauth.127.0.0.1.sslip.io/api/healthz',
40+
reuseExistingServer: true,
41+
gracefulShutdown: {
42+
signal: 'SIGINT',
43+
timeout: 1000,
44+
},
45+
},
46+
});

0 commit comments

Comments
 (0)