Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
315 changes: 315 additions & 0 deletions .github/actions/run-e2e-suite/action.yml

Large diffs are not rendered by default.

35 changes: 35 additions & 0 deletions .github/workflows/e2e-nightly.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: 🌙 E2E Nightly (latest ThunderID)

# Runs the same E2E suite as pr-builder.yml's `e2e` job, against the same always-latest ThunderID
# release. The two differ only in trigger: this one runs on a schedule regardless of PR activity,
# so a ThunderID release that breaks something surfaces even on a day with no relevant PR open —
# the per-PR job (see pr-builder.yml) is label-gated and opt-in, not scheduled.

on:
schedule:
- cron: "30 18 * * *" # 18:30 UTC daily
workflow_dispatch:

env:
NODE_VERSION: "lts/*"

jobs:
e2e-nightly:
name: 🌙 E2E (sample apps, latest ThunderID)
runs-on: ubuntu-latest
timeout-minutes: 40
# Least privilege: this job never pushes, comments, or writes to the repo — only the default
# GITHUB_TOKEN's read access is needed for checkout.
permissions:
contents: read
steps:
- name: 📥 Checkout Code
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
persist-credentials: false

- name: 🌙 Run E2E suite
uses: ./.github/actions/run-e2e-suite
with:
node-version: ${{ env.NODE_VERSION }}
artifact-name: playwright-report-nightly
32 changes: 31 additions & 1 deletion .github/workflows/pr-builder.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: 👷🛠️ PR Builder

on:
pull_request:
types: [opened, synchronize, reopened]
types: [opened, synchronize, reopened, labeled]
merge_group:
workflow_dispatch:

Expand Down Expand Up @@ -98,3 +98,33 @@ jobs:

- name: 🧪 Test
run: pnpm --filter '!./packages/**' --filter '!./samples/**' test

e2e:
name: 🎭 E2E (sample apps)
# Label-gated, mirroring thunderid's own pr-builder.yml `trigger-pr-builder` pattern — a real
# backend + six sample apps is expensive enough that it shouldn't run on every push by
# default. Always runs on merge_group/workflow_dispatch.
if: >-
github.event_name != 'pull_request' ||
contains(github.event.pull_request.labels.*.name, 'trigger-e2e')
Comment thread
coderabbitai[bot] marked this conversation as resolved.
runs-on: ubuntu-latest
timeout-minutes: 40
# Least privilege: this job never pushes, comments, or writes to the repo — only the default
# GITHUB_TOKEN's read access is needed for checkout.
permissions:
contents: read
steps:
- name: 📥 Checkout Code
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
# Matches thunder-id/thunderid's own checkout steps: the checked-out repo never needs
# git push/fetch credentials in this job, so don't leave the token available to be
# abused by anything that runs afterward (including the third-party composite actions
# invoked later in this job).
persist-credentials: false

- name: 🎭 Run E2E suite
uses: ./.github/actions/run-e2e-suite
with:
node-version: ${{ env.NODE_VERSION }}
artifact-name: playwright-report
8 changes: 8 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,12 @@ export default [
'@thunderid/copyright-header': 'off',
},
},
{
// Progress/setup/teardown output is the intended UX for E2E tooling, not stray debug
// logging — same reasoning thunderid's own tests/e2e applies (no no-console rule there).
files: ['tests/e2e/**'],
rules: {
'no-console': 'off',
},
},
];
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"devDependencies": {
"turbo": "2.10.2",
"@thunderid/eslint-plugin": "catalog:",
"@thunderid/prettier-config": "catalog:",
"eslint": "catalog:",
"prettier": "catalog:",
"rimraf": "catalog:",
Expand Down
58 changes: 53 additions & 5 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion pnpm-workspace.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
packages:
- packages/*
- samples/*/*
- tests/*

allowBuilds:
'@parcel/watcher': true
Expand Down Expand Up @@ -38,7 +39,7 @@ overrides:
# packages/nextjs's `next` > `postcss` > `nanoid` (next still pins `postcss@8.4.31`, which pulls
# an old nanoid — see the `postcss` override below), and v5 via packages/nuxt's
# `@nuxt/devtools` > `@vue/devtools-core` > `nanoid`.
nanoid@3: 3.3.17
nanoid@3: 3.3.18
nanoid@5: 5.1.16
# JUSTIFICATION: Fixes GHSA-qx2v-qp2m-jg93 (XSS via unescaped `</style>` in stringify output),
# GHSA-6g55-p6wh-862q / GHSA-r28c-9q8g-f849 / GHSA-fxqj-rqcc-2cmp (arbitrary file read via
Expand Down
18 changes: 18 additions & 0 deletions tests/e2e/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Local overrides for the E2E suite.
#
# You normally do NOT need to create this file: run-e2e.sh auto-generates a working .env from
# defaults.env on first run. Copy this file to .env only if you want to override specific values
# (e.g. pointing at an already-running backend, or different sample app ports).
#
# SERVER_URL=https://localhost:8090
# ADMIN_USERNAME=admin
# ADMIN_PASSWORD=admin
# ADMIN_TOKEN=
# TEST_USER_USERNAME=e2e-test-user
# TEST_USER_PASSWORD=E2ePassword@123
# BROWSER_APP_URL=http://localhost:5173
# REACT_APP_URL=http://localhost:5174
# VUE_APP_URL=http://localhost:5175
# NEXTJS_APP_URL=http://localhost:3001
# NUXT_APP_URL=http://localhost:3002
# EXPRESS_APP_URL=http://localhost:3000
8 changes: 8 additions & 0 deletions tests/e2e/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Downloaded by run-e2e.sh (npx thunderid) — a full release distribution, not source.
thunderid/
.npx-thunderid-home/

# Playwright output
playwright-report/
test-results/
blob-report/
3 changes: 3 additions & 0 deletions tests/e2e/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Go-template + YAML hybrids (ThunderID's declarative /import format) — not valid YAML on their
# own, only once the {{ }} placeholders are rendered server-side.
thunderid-config/**/*.yaml
78 changes: 78 additions & 0 deletions tests/e2e/constants/sample-apps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Copyright 2025 The ThunderID Authors
// SPDX-License-Identifier: Apache-2.0

/**
* Sample App Registry
*
* One entry per browser-testable quickstart app under `samples/`, each with a dedicated port (so
* all apps can run concurrently against a single backend) and a dedicated OAuth2 client (declared
* with matching redirectUris in `thunderid-config/sample-apps.yaml`) so one app's E2E run never
* collides with another's. `node/quickstart` is deliberately absent from this registry — it has
* no browser UI, so no page object or port — but it still has its own OAuth2 client, see
* `NodeQuickstart` below and `tests/node-quickstart/client-credentials.spec.ts`.
*
* Ports match each dev server's own default where possible (browser/quickstart keeps Vite's 5173,
* express/quickstart keeps its hardcoded 3000) and are staggered elsewhere via each tool's own
* `--port`/`-p` flag — no sample app source changes required.
*/
export const SampleApps = {
BROWSER: {
clientId: 'JS_SDK_E2E_BROWSER',
dir: 'samples/browser/quickstart',
envVar: 'BROWSER_APP_URL',
name: 'browser/quickstart',
port: 5173,
},
EXPRESS: {
clientId: 'JS_SDK_E2E_EXPRESS',
dir: 'samples/express/quickstart',
envVar: 'EXPRESS_APP_URL',
name: 'express/quickstart',
port: 3000,
},
NEXTJS: {
clientId: 'JS_SDK_E2E_NEXTJS',
dir: 'samples/nextjs/quickstart',
envVar: 'NEXTJS_APP_URL',
name: 'nextjs/quickstart',
port: 3001,
},
NUXT: {
clientId: 'JS_SDK_E2E_NUXT',
dir: 'samples/nuxt/quickstart',
envVar: 'NUXT_APP_URL',
name: 'nuxt/quickstart',
port: 3002,
},
REACT: {
clientId: 'JS_SDK_E2E_REACT',
dir: 'samples/react/quickstart',
envVar: 'REACT_APP_URL',
name: 'react/quickstart',
port: 5174,
},
VUE: {
clientId: 'JS_SDK_E2E_VUE',
dir: 'samples/vue/quickstart',
envVar: 'VUE_APP_URL',
name: 'vue/quickstart',
port: 5175,
},
} as const;

export type SampleAppKey = keyof typeof SampleApps;

/**
* The one non-browser sample under E2E test: no page, no port, authenticates itself via
* client_credentials (see AuthService.mjs) rather than signing a user in.
*/
export const NodeQuickstart = {
clientId: 'JS_SDK_E2E_NODE',
dir: 'samples/node/quickstart',
name: 'node/quickstart',
} as const;

/** Resolves an app's base URL from its env var, falling back to `http://localhost:<port>`. */
export function sampleAppUrl(app: (typeof SampleApps)[SampleAppKey]): string {
return process.env[app.envVar] ?? `http://localhost:${app.port}`;
}
22 changes: 22 additions & 0 deletions tests/e2e/constants/timeouts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright 2025 The ThunderID Authors
// SPDX-License-Identifier: Apache-2.0

/**
* Global timeouts for the E2E suite.
*/
export const Timeouts = {
/** Default timeout for UI actions (clicks, fills). */
DEFAULT_ACTION: 15000,

/** Timeout for checking element visibility. */
ELEMENT_VISIBILITY: 10000,

/** Timeout for a redirect-flow round trip (app -> gate -> app). */
REDIRECT: 20000,

/** Budget for a suite-level beforeAll/afterAll that provisions server state (e.g. a test user). */
SUITE_SETUP: 60 * 1000,

/** Per-test timeout (playwright.config.ts). */
GLOBAL_TEST: 60 * 1000,
} as const;
18 changes: 18 additions & 0 deletions tests/e2e/defaults.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Canonical fixed dataset for running the E2E suite unattended (run-e2e.sh and CI both load this
# file). Override any of these via a real .env file locally, or via repository Secrets/Variables
# in CI.
SERVER_URL=https://localhost:8090
ADMIN_USERNAME=admin
ADMIN_PASSWORD=admin
TEST_USER_USERNAME=e2e-test-user
TEST_USER_PASSWORD=E2ePassword@123
Comment thread
coderabbitai[bot] marked this conversation as resolved.

# One dedicated port per sample app — see constants/sample-apps.ts, which is the source of truth
# these must match (both the ports here and the OAuth2 redirectUris declared for each app in
# thunderid-config/sample-apps.yaml).
BROWSER_APP_URL=http://localhost:5173
REACT_APP_URL=http://localhost:5174
VUE_APP_URL=http://localhost:5175
NEXTJS_APP_URL=http://localhost:3001
NUXT_APP_URL=http://localhost:3002
EXPRESS_APP_URL=http://localhost:3000
Loading
Loading