@andwhy/e2e-pipeline 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/README.md +106 -0
  2. package/dist/bin/init.d.ts +2 -0
  3. package/dist/bin/init.js +128 -0
  4. package/dist/utils/copy.d.ts +23 -0
  5. package/dist/utils/copy.js +249 -0
  6. package/dist/utils/format.d.ts +1 -0
  7. package/dist/utils/format.js +49 -0
  8. package/dist/utils/install.d.ts +2 -0
  9. package/dist/utils/install.js +54 -0
  10. package/dist/utils/prompt.d.ts +2 -0
  11. package/dist/utils/prompt.js +17 -0
  12. package/package.json +37 -0
  13. package/templates/claude/agents/playwright-test-generator.md +184 -0
  14. package/templates/claude/agents/playwright-test-healer.md +70 -0
  15. package/templates/claude/agents/playwright-test-planner.md +135 -0
  16. package/templates/claude/partials/_robustness-core.md +69 -0
  17. package/templates/claude/rules/playwright-workflow.md +203 -0
  18. package/templates/claude/skills/playwright-test-pipeline/SKILL.md +201 -0
  19. package/templates/github/scripts/playwright-pr-comment.mjs +95 -0
  20. package/templates/github/workflows/checkly-deploy.yml +64 -0
  21. package/templates/github/workflows/pr-playwright-tests.yml +154 -0
  22. package/templates/shared/checkly.config.template.mjs +58 -0
  23. package/templates/shared/checkly.config.template.ts +58 -0
  24. package/templates/shared/playwright.config.template.mjs +44 -0
  25. package/templates/shared/playwright.config.template.ts +44 -0
  26. package/templates/shared/playwright.mcp.config.template.mjs +12 -0
  27. package/templates/shared/playwright.mcp.config.template.ts +12 -0
  28. package/templates/shared/preview-consent-setup.template.js +32 -0
  29. package/templates/shared/preview-consent-setup.template.ts +33 -0
  30. package/templates/shared/seed.spec.js +5 -0
  31. package/templates/shared/seed.spec.ts +5 -0
@@ -0,0 +1,154 @@
1
+ name: PR Playwright tests
2
+
3
+ # Runs the suite against the PR's own Vercel preview. `deployment_status`
4
+ # fires once the preview is live and carries its URL; the preview is built
5
+ # from the PR branch, so the PR's changes exist on the target.
6
+ # Needs repo secret VERCEL_AUTOMATION_BYPASS_SECRET when previews are
7
+ # protected; the scaffolded helper turns it into Vercel's bypass cookie.
8
+ on:
9
+ deployment_status
10
+
11
+ # Vercel's payload has no branch, only the SHA — the job below resolves it.
12
+ run-name: Playwright · ${{ github.event.deployment.environment }}
13
+
14
+ permissions:
15
+ contents: read
16
+ pull-requests: write
17
+
18
+ jobs:
19
+ resolve-branch:
20
+ if: >-
21
+ github.event.deployment_status.state == 'success' &&
22
+ github.event.deployment.environment == 'Preview'
23
+ runs-on: ubuntu-latest
24
+ timeout-minutes: 2
25
+ outputs:
26
+ branch: ${{ steps.pr.outputs.branch }}
27
+ label: ${{ steps.pr.outputs.label }}
28
+ number: ${{ steps.pr.outputs.number }}
29
+ steps:
30
+ - name: Resolve the PR branch for this commit
31
+ id: pr
32
+ env:
33
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
34
+ SHA: ${{ github.event.deployment.ref }}
35
+ REPO: ${{ github.repository }}
36
+ run: |
37
+ # No associated PR (plain branch push) falls back to the SHA.
38
+ pr=$(gh api "repos/$REPO/commits/$SHA/pulls" --jq \
39
+ 'if length > 0 then "\(.[0].number)\t\(.[0].head.ref)\t\(.[0].title)" else "" end' \
40
+ 2>/dev/null || true)
41
+ branch=$(printf '%s' "$pr" | cut -f2)
42
+ number=$(printf '%s' "$pr" | cut -f1)
43
+ title=$(printf '%s' "$pr" | cut -f3)
44
+
45
+ echo "branch=${branch:-$SHA}" >> "$GITHUB_OUTPUT"
46
+ echo "number=${number}" >> "$GITHUB_OUTPUT"
47
+ if [ -n "$number" ]; then
48
+ echo "label=#${number} ${title}" >> "$GITHUB_OUTPUT"
49
+ else
50
+ echo "label=${branch:-$SHA}" >> "$GITHUB_OUTPUT"
51
+ fi
52
+
53
+ playwright:
54
+ name: Playwright · ${{ needs.resolve-branch.outputs.label }}
55
+ needs: resolve-branch
56
+ # A re-push cancels the stale run instead of racing an outdated preview.
57
+ concurrency:
58
+ group: pr-playwright-${{ needs.resolve-branch.outputs.branch }}
59
+ cancel-in-progress: true
60
+ runs-on: ubuntu-latest
61
+ # Red informs, branch protection decides — the check is not required.
62
+ timeout-minutes: 10
63
+
64
+ steps:
65
+ - name: Check out the deployed ref
66
+ uses: actions/checkout@v5
67
+ with:
68
+ ref: ${{ github.event.deployment.ref }}
69
+
70
+ - name: Set up pnpm
71
+ uses: pnpm/action-setup@v4
72
+
73
+ - name: Set up Node
74
+ uses: actions/setup-node@v5
75
+ with:
76
+ node-version: 22
77
+ cache: pnpm
78
+
79
+ # --ignore-scripts: only the tests run here; project postinstall hooks
80
+ # are not needed and may want CMS tokens CI does not have.
81
+ - name: Install dependencies
82
+ run: pnpm install --frozen-lockfile --ignore-scripts
83
+
84
+ - name: Install Playwright browsers
85
+ run: pnpm playwright install --with-deps chromium
86
+
87
+ # Chrome only for speed; Firefox/Edge run on Checkly against production.
88
+ # --global-timeout instead of a step timeout so reports still flush.
89
+ - name: Run Playwright tests against the preview
90
+ id: tests
91
+ timeout-minutes: 9
92
+ env:
93
+ BASE_URL: ${{ github.event.deployment_status.target_url }}
94
+ VERCEL_AUTOMATION_BYPASS_SECRET: ${{ secrets.VERCEL_AUTOMATION_BYPASS_SECRET }}
95
+ PLAYWRIGHT_JSON_OUTPUT_NAME: playwright-results.json
96
+ run: |
97
+ pnpm playwright test \
98
+ --project=Chrome \
99
+ --global-timeout 360000 \
100
+ --reporter=list,html,json
101
+
102
+ - name: Upload HTML report
103
+ if: always()
104
+ uses: actions/upload-artifact@v4
105
+ with:
106
+ name: playwright-report-${{ github.run_id }}
107
+ path: |
108
+ playwright-report/
109
+ playwright-results.json
110
+ if-no-files-found: warn
111
+ retention-days: 7
112
+
113
+ # Reporting only — must not turn the check red.
114
+ - name: Comment the result on the PR
115
+ if: always() && needs.resolve-branch.outputs.number != ''
116
+ continue-on-error: true
117
+ env:
118
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
119
+ REPO: ${{ github.repository }}
120
+ PR: ${{ needs.resolve-branch.outputs.number }}
121
+ PREVIEW_URL: ${{ github.event.deployment_status.target_url }}
122
+ RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
123
+ run: |
124
+ node .github/scripts/playwright-pr-comment.mjs \
125
+ playwright-results.json > comment.md
126
+
127
+ # Update the existing comment so re-pushes do not stack.
128
+ marker='<!-- playwright-preview-report -->'
129
+ id=$(gh api "repos/$REPO/issues/$PR/comments" --paginate \
130
+ --jq "map(select(.body | startswith(\"$marker\"))) | .[0].id // empty")
131
+
132
+ if [ -n "$id" ]; then
133
+ gh api -X PATCH "repos/$REPO/issues/comments/$id" -F body=@comment.md
134
+ else
135
+ gh api -X POST "repos/$REPO/issues/$PR/comments" -F body=@comment.md
136
+ fi
137
+
138
+ - name: Write result to job summary
139
+ if: always()
140
+ env:
141
+ OUTCOME: ${{ steps.tests.outcome }}
142
+ PREVIEW_URL: ${{ github.event.deployment_status.target_url }}
143
+ run: |
144
+ {
145
+ echo "## Playwright (preview)"
146
+ echo ""
147
+ echo "- Target: $PREVIEW_URL"
148
+ echo "- Browser: Chrome only"
149
+ if [ "$OUTCOME" = "success" ]; then
150
+ echo "- Result: all tests passed"
151
+ else
152
+ echo "- Result: **failures present** — see the uploaded \`playwright-report\` artifact"
153
+ fi
154
+ } >> "$GITHUB_STEP_SUMMARY"
@@ -0,0 +1,58 @@
1
+ import { defineConfig } from "checkly";
2
+
3
+ // -- Multi-domain example (uncomment and adapt) --
4
+ // const domains = [
5
+ // { name: "example.com", url: "https://example.com" },
6
+ // { name: "example.de", url: "https://example.de" },
7
+ // ]
8
+
9
+ /**
10
+ * See https://www.checklyhq.com/docs/cli/project-structure/
11
+ */
12
+ export default defineConfig({
13
+ projectName: "my-app",
14
+ /** A logical ID that needs to be unique across your Checkly account.
15
+ * See https://www.checklyhq.com/docs/cli/constructs/
16
+ */
17
+ logicalId: "my-app",
18
+ repoUrl: "https://github.com/your-org/your-repo",
19
+ checks: {
20
+ frequency: 180,
21
+ locations: ["eu-central-1"],
22
+ runtimeId: "2025.04",
23
+ playwrightConfigPath: "./playwright.config.mjs",
24
+ playwrightChecks: [
25
+ {
26
+ logicalId: "my-app-check-suite",
27
+ // Change the name to match your brand or product.
28
+ name: "my-app",
29
+ // Checkly's runner gets a bundle of test files, not the repo, so a
30
+ // project postinstall (codegen etc.) fails there on missing files.
31
+ // pnpm 10 skips dependency build scripts by default, so this only
32
+ // disables the project's own hooks; browsers come from the runtime.
33
+ installCommand: "pnpm install --ignore-scripts",
34
+ // Deploy only the e2e suite as monitors. Without this the whole
35
+ // Playwright suite runs per domain, so unit tests and regression
36
+ // guards that never needed a deployment become recurring paid checks.
37
+ testCommand: "npx playwright test tests/e2e",
38
+ environmentVariables: [
39
+ // Set this to your production URL.
40
+ { key: "BASE_URL", value: "http://localhost:3000" },
41
+ ],
42
+ },
43
+ // Add more suites for additional domains:
44
+ // {
45
+ // logicalId: "my-app-check-suite-de",
46
+ // name: "my-app (DE)",
47
+ // environmentVariables: [
48
+ // { key: "BASE_URL", value: "https://example.de" },
49
+ // ],
50
+ // },
51
+ ],
52
+ },
53
+ cli: {
54
+ runLocation: "eu-west-1",
55
+ reporters: ["list"],
56
+ retries: 1,
57
+ },
58
+ });
@@ -0,0 +1,58 @@
1
+ import { defineConfig } from "checkly";
2
+
3
+ // -- Multi-domain example (uncomment and adapt) --
4
+ // const domains = [
5
+ // { name: "example.com", url: "https://example.com" },
6
+ // { name: "example.de", url: "https://example.de" },
7
+ // ]
8
+
9
+ /**
10
+ * See https://www.checklyhq.com/docs/cli/project-structure/
11
+ */
12
+ export default defineConfig({
13
+ projectName: "my-app",
14
+ /** A logical ID that needs to be unique across your Checkly account.
15
+ * See https://www.checklyhq.com/docs/cli/constructs/
16
+ */
17
+ logicalId: "my-app",
18
+ repoUrl: "https://github.com/your-org/your-repo",
19
+ checks: {
20
+ frequency: 180,
21
+ locations: ["eu-central-1"],
22
+ runtimeId: "2025.04",
23
+ playwrightConfigPath: "./playwright.config.ts",
24
+ playwrightChecks: [
25
+ {
26
+ logicalId: "my-app-check-suite",
27
+ // Change the name to match your brand or product.
28
+ name: "my-app",
29
+ // Checkly's runner gets a bundle of test files, not the repo, so a
30
+ // project postinstall (codegen etc.) fails there on missing files.
31
+ // pnpm 10 skips dependency build scripts by default, so this only
32
+ // disables the project's own hooks; browsers come from the runtime.
33
+ installCommand: "pnpm install --ignore-scripts",
34
+ // Deploy only the e2e suite as monitors. Without this the whole
35
+ // Playwright suite runs per domain, so unit tests and regression
36
+ // guards that never needed a deployment become recurring paid checks.
37
+ testCommand: "npx playwright test tests/e2e",
38
+ environmentVariables: [
39
+ // Set this to your production URL.
40
+ { key: "BASE_URL", value: "http://localhost:3000" },
41
+ ],
42
+ },
43
+ // Add more suites for additional domains:
44
+ // {
45
+ // logicalId: "my-app-check-suite-de",
46
+ // name: "my-app (DE)",
47
+ // environmentVariables: [
48
+ // { key: "BASE_URL", value: "https://example.de" },
49
+ // ],
50
+ // },
51
+ ],
52
+ },
53
+ cli: {
54
+ runLocation: "eu-west-1",
55
+ reporters: ["list"],
56
+ retries: 1,
57
+ },
58
+ });
@@ -0,0 +1,44 @@
1
+ import { defineConfig, devices } from "@playwright/test";
2
+
3
+ export default defineConfig({
4
+ // e2e specs live here so other suites elsewhere under tests/ are not picked
5
+ // up. If the repo already has such a suite (unit, a11y), give it its own
6
+ // project below with its own testDir — do NOT widen this one to ./tests,
7
+ // which would run that suite in every browser project and mix the suites.
8
+ testDir: "./tests/e2e",
9
+ // Seeds are run directly by the Playwright MCP, not as tests of their own.
10
+ testIgnore: "**/seed.spec.*",
11
+ fullyParallel: true,
12
+ forbidOnly: !!process.env.CI,
13
+ retries: process.env.CI ? 2 : 0,
14
+ workers: process.env.CI ? 1 : undefined,
15
+ reporter: "html",
16
+ use: {
17
+ baseURL: process.env.BASE_URL ?? "http://localhost:3000",
18
+ trace: "on",
19
+ },
20
+ projects: [
21
+ {
22
+ name: "Chrome",
23
+ use: {
24
+ ...devices["Desktop Chrome"],
25
+ userAgent: "Chrome_Checkly_0759687ceba6",
26
+ },
27
+ },
28
+ {
29
+ name: "Firefox",
30
+ use: {
31
+ ...devices["Desktop Firefox"],
32
+ userAgent: "Firefox_Checkly_0759687ceba6",
33
+ },
34
+ },
35
+ {
36
+ name: "Edge",
37
+ use: {
38
+ ...devices["Desktop Edge"],
39
+ userAgent: "Edge_Checkly_0759687ceba6",
40
+ },
41
+ },
42
+ ],
43
+ timeout: 60_000,
44
+ });
@@ -0,0 +1,44 @@
1
+ import { defineConfig, devices } from "@playwright/test";
2
+
3
+ export default defineConfig({
4
+ // e2e specs live here so other suites elsewhere under tests/ are not picked
5
+ // up. If the repo already has such a suite (unit, a11y), give it its own
6
+ // project below with its own testDir — do NOT widen this one to ./tests,
7
+ // which would run that suite in every browser project and mix the suites.
8
+ testDir: "./tests/e2e",
9
+ // Seeds are run directly by the Playwright MCP, not as tests of their own.
10
+ testIgnore: "**/seed.spec.*",
11
+ fullyParallel: true,
12
+ forbidOnly: !!process.env.CI,
13
+ retries: process.env.CI ? 2 : 0,
14
+ workers: process.env.CI ? 1 : undefined,
15
+ reporter: "html",
16
+ use: {
17
+ baseURL: process.env.BASE_URL ?? "http://localhost:3000",
18
+ trace: "on",
19
+ },
20
+ projects: [
21
+ {
22
+ name: "Chrome",
23
+ use: {
24
+ ...devices["Desktop Chrome"],
25
+ userAgent: "Chrome_Checkly_0759687ceba6",
26
+ },
27
+ },
28
+ {
29
+ name: "Firefox",
30
+ use: {
31
+ ...devices["Desktop Firefox"],
32
+ userAgent: "Firefox_Checkly_0759687ceba6",
33
+ },
34
+ },
35
+ {
36
+ name: "Edge",
37
+ use: {
38
+ ...devices["Desktop Edge"],
39
+ userAgent: "Edge_Checkly_0759687ceba6",
40
+ },
41
+ },
42
+ ],
43
+ timeout: 60_000,
44
+ });
@@ -0,0 +1,12 @@
1
+ import { defineConfig } from "@playwright/test";
2
+ import config from "./playwright.config.mjs";
3
+
4
+ // Used only by the Playwright MCP server — .mcp.json points here, nothing else
5
+ // does. The main config ignores seed.spec.* so seeds never run as tests in
6
+ // local, CI, or Checkly runs; but the planner/generator setup_page tools
7
+ // execute seeds through the test runner, so the MCP's config must be able to
8
+ // discover them. Do not point CI or Checkly at this file.
9
+ export default defineConfig({
10
+ ...config,
11
+ testIgnore: [],
12
+ });
@@ -0,0 +1,12 @@
1
+ import { defineConfig } from "@playwright/test";
2
+ import config from "./playwright.config";
3
+
4
+ // Used only by the Playwright MCP server — .mcp.json points here, nothing else
5
+ // does. The main config ignores seed.spec.* so seeds never run as tests in
6
+ // local, CI, or Checkly runs; but the planner/generator setup_page tools
7
+ // execute seeds through the test runner, so the MCP's config must be able to
8
+ // discover them. Do not point CI or Checkly at this file.
9
+ export default defineConfig({
10
+ ...config,
11
+ testIgnore: [],
12
+ });
@@ -0,0 +1,32 @@
1
+
2
+ // Vercel deployment protection returns 401 for anonymous visitors, which is
3
+ // what Checkly's runners are. With the project's automation bypass secret in
4
+ // VERCEL_AUTOMATION_BYPASS_SECRET (Vercel project settings → Deployment
5
+ // Protection; keep the value in 1Password and pass it per run:
6
+ // pnpm checkly pw-test --env BASE_URL="<preview>" \
7
+ // --env VERCEL_AUTOMATION_BYPASS_SECRET="<secret>" -- tests/e2e/<feature>/
8
+ // ) the first navigation exchanges the secret for Vercel's bypass cookie and
9
+ // the rest of the test uses plain relative URLs. Without the secret this is a
10
+ // plain goto, so the same specs run unchanged against localhost and production.
11
+ // Never commit the secret into checkly.config — deployed monitors run against
12
+ // production and do not need it.
13
+ export const prepareVercelPreviewPage = async (page, baseURL) => {
14
+ const secret = process.env.VERCEL_AUTOMATION_BYPASS_SECRET;
15
+ if (secret) {
16
+ await page.context().setExtraHTTPHeaders({
17
+ "x-vercel-protection-bypass": secret,
18
+ "x-vercel-set-bypass-cookie": "true",
19
+ });
20
+ }
21
+ await page.goto(baseURL ?? "/");
22
+ if (secret) {
23
+ // The bypass cookie is set now. Stop sending the secret with every
24
+ // request — extraHTTPHeaders are context-wide and would carry it to
25
+ // third-party origins too.
26
+ await page.context().setExtraHTTPHeaders({});
27
+ }
28
+ };
29
+
30
+ // Projects extend this file with their own consent helpers, e.g.
31
+ // gotoPathAndAcceptCookies(page, baseURL, path) for the local cookie banner —
32
+ // that part is CMP-specific and stays project-owned.
@@ -0,0 +1,33 @@
1
+ import type { Page } from "@playwright/test";
2
+
3
+ // Vercel deployment protection returns 401 for anonymous visitors, which is
4
+ // what Checkly's runners are. With the project's automation bypass secret in
5
+ // VERCEL_AUTOMATION_BYPASS_SECRET (Vercel project settings → Deployment
6
+ // Protection; keep the value in 1Password and pass it per run:
7
+ // pnpm checkly pw-test --env BASE_URL="<preview>" \
8
+ // --env VERCEL_AUTOMATION_BYPASS_SECRET="<secret>" -- tests/e2e/<feature>/
9
+ // ) the first navigation exchanges the secret for Vercel's bypass cookie and
10
+ // the rest of the test uses plain relative URLs. Without the secret this is a
11
+ // plain goto, so the same specs run unchanged against localhost and production.
12
+ // Never commit the secret into checkly.config — deployed monitors run against
13
+ // production and do not need it.
14
+ export const prepareVercelPreviewPage = async (page: Page, baseURL?: string) => {
15
+ const secret = process.env.VERCEL_AUTOMATION_BYPASS_SECRET;
16
+ if (secret) {
17
+ await page.context().setExtraHTTPHeaders({
18
+ "x-vercel-protection-bypass": secret,
19
+ "x-vercel-set-bypass-cookie": "true",
20
+ });
21
+ }
22
+ await page.goto(baseURL ?? "/");
23
+ if (secret) {
24
+ // The bypass cookie is set now. Stop sending the secret with every
25
+ // request — extraHTTPHeaders are context-wide and would carry it to
26
+ // third-party origins too.
27
+ await page.context().setExtraHTTPHeaders({});
28
+ }
29
+ };
30
+
31
+ // Projects extend this file with their own consent helpers, e.g.
32
+ // gotoPathAndAcceptCookies(page, baseURL, path) for the local cookie banner —
33
+ // that part is CMP-specific and stays project-owned.
@@ -0,0 +1,5 @@
1
+ import { test } from "@playwright/test";
2
+
3
+ test("seed", async ({ page }) => {
4
+ await page.goto("/");
5
+ });
@@ -0,0 +1,5 @@
1
+ import { test } from "@playwright/test";
2
+
3
+ test("seed", async ({ page }) => {
4
+ await page.goto("/");
5
+ });