Skip to content

Commit 38be80c

Browse files
authored
Fix E2E tests, make them 8x faster (#2722)
* test sharding * test * Update globalSetup.ts * Update e2e-fixes.yml * fix * further optimizations * some more optimizations * skip broken * try using load * more network idle purging * 8 shards * split * test fix * maybe fix * actually pass license * restore * lint
1 parent 7201b77 commit 38be80c

30 files changed

Lines changed: 533 additions & 410 deletions

.github/workflows/e2e.yml

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@ jobs:
1212
test:
1313
runs-on:
1414
- codebuild-defguard-core-runner-${{ github.run_id }}-${{ github.run_attempt }}
15-
instance-size:2xlarge
16-
outputs:
17-
tests_ok: ${{ steps.run-test.outcome == 'success' }}
15+
instance-size:medium
16+
strategy:
17+
fail-fast: false
18+
matrix:
19+
shard: [1, 2, 3, 4, 5, 6, 7, 8]
1820

1921
steps:
2022
- uses: actions/checkout@v6
@@ -30,13 +32,10 @@ jobs:
3032

3133
- name: Export image tag
3234
run: |
33-
# strip "refs/heads.” to get just the branch name
3435
BRANCH=${GITHUB_REF#refs/heads/}
3536
if [[ "$BRANCH" == release/* ]]; then
36-
# replace '/' with '-' on release branches
3737
IMAGE_TAG=${BRANCH//\//-}
3838
else
39-
# main/dev branch
4039
IMAGE_TAG=$BRANCH
4140
fi
4241
echo "IMAGE_TAG=$IMAGE_TAG" >> $GITHUB_ENV
@@ -76,33 +75,47 @@ jobs:
7675
working-directory: ./e2e
7776
run: pnpm install --frozen-lockfile
7877

78+
- name: Cache Playwright browsers
79+
id: playwright-cache
80+
uses: actions/cache@v5
81+
with:
82+
path: ~/.cache/ms-playwright
83+
key: ${{ runner.os }}-playwright-${{ hashFiles('e2e/pnpm-lock.yaml') }}
84+
restore-keys: |
85+
${{ runner.os }}-playwright-
86+
7987
- name: Install playwright chromium
8088
working-directory: ./e2e
81-
run: npx playwright install chromium
89+
run: |
90+
if [[ "${{ steps.playwright-cache.outputs.cache-hit }}" == "true" ]]; then
91+
# Browsers are cached; only install missing system dependencies.
92+
npx playwright install-deps chromium
93+
else
94+
npx playwright install --with-deps chromium
95+
fi
8296
8397
- name: run tests
8498
id: run-test
85-
continue-on-error: true
8699
working-directory: ./e2e
87100
env:
88101
DEFGUARD_LICENSE_KEY: ${{ secrets.DEFGUARD_LICENSE_KEY }}
89-
run: pnpm test
102+
run: pnpm test --shard=${{ matrix.shard }}/8
90103

91104
- name: Stop compose
92105
if: always()
93106
run: docker compose --file './docker-compose.e2e.yaml' down
94107

95108
- uses: actions/upload-artifact@v7
96-
if: steps.run-test.outcome == 'failure'
109+
if: failure()
97110
with:
98-
name: playwright-report
111+
name: playwright-report-shard-${{ matrix.shard }}
99112
path: |
100113
./e2e/playwright-report
101114
retention-days: 7
102115

103116
trigger-dev-deploy:
104117
needs: test
105-
if: ${{ github.event_name != 'pull_request' && github.ref_name == 'dev' }}
118+
if: ${{ github.event_name != 'pull_request' && github.ref_name == 'dev' && needs.test.result == 'success' }}
106119
uses: ./.github/workflows/dev-deployment.yml
107120
secrets: inherit
108121

@@ -111,6 +124,6 @@ jobs:
111124
if: |
112125
github.event_name != 'pull_request' &&
113126
startsWith(github.ref_name, 'release/') &&
114-
needs.test.outputs.tests_ok == 'true'
127+
needs.test.result == 'success'
115128
uses: ./.github/workflows/staging-deployment.yml
116129
secrets: inherit

docker-compose.e2e.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ services:
1616
DEFGUARD_DB_PASSWORD: defguard
1717
DEFGUARD_DB_NAME: defguard
1818
DEFGUARD_URL: http://localhost:8000
19+
DEFGUARD_LICENSE_KEY: ${DEFGUARD_LICENSE_KEY:-}
1920
RUST_BACKTRACE: 1
2021
ports:
2122
- "8000:8000"

e2e/config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ const defaultConfig: TestsConfig = {
1313
BASE_URL: 'http://localhost:8000',
1414
CORE_BASE_URL: 'http://localhost:8000/api/v1',
1515
ENROLLMENT_URL: 'http://localhost:8080',
16-
TEST_TIMEOUT: 240,
16+
TEST_TIMEOUT: 60,
1717
};
1818

1919
const envConfig: Partial<TestsConfig> = {

e2e/playwright.config.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,14 @@ export default defineConfig({
2828
globalSetup: './utils/globalSetup',
2929
timeout: testsConfig.TEST_TIMEOUT * 1000,
3030
testDir: './tests',
31+
// Exclude files that consist entirely of skipped tests to avoid Playwright
32+
// collecting and reporting them as empty suites on every shard.
33+
testIgnore: [
34+
'**/enrollment.spec.ts',
35+
'**/externalopenid.spec.ts',
36+
'**/externalopenidmfa.spec.ts',
37+
'**/openid.spec.ts',
38+
],
3139
/* Run tests in files in parallel */
3240
fullyParallel: false,
3341
/* Fail the build on CI if you accidentally left test.only in the source code. */

e2e/tests/apiTokens.spec.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { expect, test } from '@playwright/test';
2+
3+
import { defaultUserAdmin, routes, testUserTemplate } from '../config';
4+
import { User } from '../types';
5+
import { createUser } from '../utils/controllers/createUser';
6+
import { loginBasic } from '../utils/controllers/login';
7+
import { dockerRestart } from '../utils/docker';
8+
import { waitForBase } from '../utils/waitForBase';
9+
10+
test.describe('API tokens management', () => {
11+
let testUser: User;
12+
const token_name = 'test token name';
13+
14+
test.beforeEach(() => {
15+
dockerRestart();
16+
testUser = { ...testUserTemplate, username: 'test' };
17+
});
18+
19+
test('Add API token as default admin', async ({ page }) => {
20+
await waitForBase(page);
21+
await loginBasic(page, defaultUserAdmin);
22+
await page.goto(
23+
routes.base + routes.profile + defaultUserAdmin.username + routes.tab.api_tokens,
24+
);
25+
await page.getByTestId('add-token').click();
26+
await page.getByTestId('field-name').fill(token_name);
27+
await page.getByTestId('submit').click();
28+
const api_token = await page.getByTestId('copy-field').textContent();
29+
await page.getByTestId('close').click();
30+
31+
const row = await page
32+
.locator('.table-row-container')
33+
.filter({ hasText: token_name });
34+
await row.locator('.icon-button').click();
35+
await page.getByTestId('delete').click();
36+
await page.locator('button[data-variant="critical"]').click();
37+
await expect(row).not.toBeVisible();
38+
expect(api_token).toBeDefined();
39+
});
40+
41+
test('Add API token as new user with admin privileges', async ({ page, browser }) => {
42+
await waitForBase(page);
43+
await createUser(browser, testUser, ['admin']);
44+
await loginBasic(page, testUser);
45+
await page.goto(
46+
routes.base + routes.profile + testUser.username + routes.tab.api_tokens,
47+
);
48+
await page.getByTestId('add-token').click();
49+
await page.getByTestId('field-name').fill(token_name);
50+
await page.getByTestId('submit').click();
51+
const api_token = await page.getByTestId('copy-field').textContent();
52+
await page.getByTestId('close').click();
53+
54+
const row = await page
55+
.locator('.table-row-container')
56+
.filter({ hasText: token_name });
57+
await row.locator('.icon-button').click();
58+
await page.getByTestId('delete').click();
59+
await page.locator('button[data-variant="critical"]').click();
60+
await expect(row).not.toBeVisible();
61+
expect(api_token).toBeDefined();
62+
});
63+
});

0 commit comments

Comments
 (0)