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
85 changes: 72 additions & 13 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,7 @@ jobs:
echo "BUGDROP_CANARY_RESULT_FILE=test-results/issue-canary-result.json" >> "$GITHUB_ENV"
echo "BUGDROP_CANARY_ATTEMPT_FILE=$RUNNER_TEMP/bugdrop-canary-attempt-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" >> "$GITHUB_ENV"
echo "EXACT_WIDGET_FIXTURE_PATH=$RUNNER_TEMP/bugdrop-exact-preview-widget-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}.js" >> "$GITHUB_ENV"
echo "EXACT_CLASSIC_WIDGET_FIXTURE_PATH=$RUNNER_TEMP/bugdrop-exact-preview-classic-widget-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}.js" >> "$GITHUB_ENV"
echo "EXPECTED_WORKER_SHA=${GITHUB_SHA}" >> "$GITHUB_ENV"
echo "EXPECTED_WIDGET_ORIGIN=https://bugdrop-preview.neonwatty.workers.dev" >> "$GITHUB_ENV"
echo "LIVE_TARGET=preview" >> "$GITHUB_ENV"
Expand All @@ -402,11 +403,23 @@ jobs:
env:
BUGDROP_CANARY_GITHUB_TOKEN: ${{ secrets.BUGDROP_CANARY_GITHUB_TOKEN }}

- name: Build classic preview widget
run: |
CLASSIC_OUTPUT="$RUNNER_TEMP/classic-preview-widget"
BUGDROP_DEFAULT_FLOW_RUNTIME=fixed node scripts/build-widget.js \
--mode development \
--source-dir . \
--output-dir "$CLASSIC_OUTPUT" \
--development-id "merge-group-${GITHUB_SHA}-classic"

- name: Build all
run: BUGDROP_BUILD_MODE=development BUGDROP_DEVELOPMENT_ID="merge-group-${GITHUB_SHA}" make build-all

- name: Record expected preview widget hash
run: echo "EXPECTED_WIDGET_SHA256=$(shasum -a 256 public/widget.js | awk '{print $1}')" >> "$GITHUB_ENV"
- name: Record expected preview widget hashes
run: |
cp "$RUNNER_TEMP/classic-preview-widget/widget.js" public/widget.classic.js
echo "EXPECTED_WIDGET_SHA256=$(shasum -a 256 public/widget.js | awk '{print $1}')" >> "$GITHUB_ENV"
echo "EXPECTED_CLASSIC_WIDGET_SHA256=$(shasum -a 256 public/widget.classic.js | awk '{print $1}')" >> "$GITHUB_ENV"

- name: Deploy to preview
run: npx wrangler deploy --env preview --var "BUILD_SHA:$GITHUB_SHA"
Expand Down Expand Up @@ -456,6 +469,30 @@ jobs:
echo "Preview widget did not serve expected asset $EXPECTED_WIDGET_SHA256"
exit 1

- name: Wait for exact classic preview widget asset
run: |
WIDGET_URL="$EXPECTED_WIDGET_ORIGIN/widget.classic.js"
CANDIDATE_PATH="$EXACT_CLASSIC_WIDGET_FIXTURE_PATH.candidate"
mkdir -p "$(dirname "$EXACT_CLASSIC_WIDGET_FIXTURE_PATH")"
echo "Waiting for $WIDGET_URL to serve $EXPECTED_CLASSIC_WIDGET_SHA256..."
for i in $(seq 1 30); do
if ! curl -sSf "$WIDGET_URL" -o "$CANDIDATE_PATH"; then
echo "Attempt $i/30 could not download the classic preview widget; waiting 5s..."
sleep 5
continue
fi
ACTUAL_SHA="$(shasum -a 256 "$CANDIDATE_PATH" | awk '{print $1}')"
if [ "$ACTUAL_SHA" = "$EXPECTED_CLASSIC_WIDGET_SHA256" ]; then
mv "$CANDIDATE_PATH" "$EXACT_CLASSIC_WIDGET_FIXTURE_PATH"
echo "Classic preview widget asset matched after $((i * 5))s"
exit 0
fi
echo "Attempt $i/30 served $ACTUAL_SHA; waiting 5s..."
sleep 5
done
echo "Classic preview widget did not serve expected asset $EXPECTED_CLASSIC_WIDGET_SHA256"
exit 1

- name: Verify fixed preview venue
run: |
BYPASS_ARGS=()
Expand All @@ -470,36 +507,58 @@ jobs:
env:
VERCEL_AUTOMATION_BYPASS_SECRET: ${{ secrets.VERCEL_AUTOMATION_BYPASS_SECRET }}

- name: Run live E2E tests
run: npx playwright test --project=chromium-live --workers=1 --retries=0
- name: Run classic live E2E tests
run: >-
EXACT_WIDGET_FIXTURE_PATH="$EXACT_CLASSIC_WIDGET_FIXTURE_PATH"
EXPECTED_WIDGET_SHA256="$EXPECTED_CLASSIC_WIDGET_SHA256"
npx playwright test --project=chromium-live --workers=1 --retries=0
env:
VERCEL_AUTOMATION_BYPASS_SECRET: ${{ secrets.VERCEL_AUTOMATION_BYPASS_SECRET }}

- name: Run composable FlowConfig live E2E tests
run: npx playwright test --project=chromium-flow-live --workers=1 --retries=0
env:
VERCEL_AUTOMATION_BYPASS_SECRET: ${{ secrets.VERCEL_AUTOMATION_BYPASS_SECRET }}

- name: Run live Radix E2E tests
run: make test-live-radix
- name: Run classic live Radix E2E tests
run: >-
EXACT_WIDGET_FIXTURE_PATH="$EXACT_CLASSIC_WIDGET_FIXTURE_PATH"
EXPECTED_WIDGET_SHA256="$EXPECTED_CLASSIC_WIDGET_SHA256"
make test-live-radix
env:
VERCEL_AUTOMATION_BYPASS_SECRET: ${{ secrets.VERCEL_AUTOMATION_BYPASS_SECRET }}

- name: Run Chromium live cross-browser smoke
run: make test-live-cross-browser BROWSER=chromium
- name: Run classic Chromium live cross-browser smoke
run: >-
EXACT_WIDGET_FIXTURE_PATH="$EXACT_CLASSIC_WIDGET_FIXTURE_PATH"
EXPECTED_WIDGET_SHA256="$EXPECTED_CLASSIC_WIDGET_SHA256"
make test-live-cross-browser BROWSER=chromium
env:
VERCEL_AUTOMATION_BYPASS_SECRET: ${{ secrets.VERCEL_AUTOMATION_BYPASS_SECRET }}

- name: Run Firefox live cross-browser smoke
run: make test-live-cross-browser BROWSER=firefox
- name: Run classic Firefox live cross-browser smoke
run: >-
EXACT_WIDGET_FIXTURE_PATH="$EXACT_CLASSIC_WIDGET_FIXTURE_PATH"
EXPECTED_WIDGET_SHA256="$EXPECTED_CLASSIC_WIDGET_SHA256"
make test-live-cross-browser BROWSER=firefox
env:
VERCEL_AUTOMATION_BYPASS_SECRET: ${{ secrets.VERCEL_AUTOMATION_BYPASS_SECRET }}

- name: Run WebKit live cross-browser smoke
run: make test-live-cross-browser BROWSER=webkit
- name: Run classic WebKit live cross-browser smoke
run: >-
EXACT_WIDGET_FIXTURE_PATH="$EXACT_CLASSIC_WIDGET_FIXTURE_PATH"
EXPECTED_WIDGET_SHA256="$EXPECTED_CLASSIC_WIDGET_SHA256"
make test-live-cross-browser BROWSER=webkit
env:
VERCEL_AUTOMATION_BYPASS_SECRET: ${{ secrets.VERCEL_AUTOMATION_BYPASS_SECRET }}

- name: Run one structured real-Issue canary
run: |
mkdir -p "$(dirname "$BUGDROP_CANARY_ATTEMPT_FILE")"
: > "$BUGDROP_CANARY_ATTEMPT_FILE"
npx playwright test e2e/widget.issue-canary.spec.ts --project=chromium-issue-canary --workers=1 --retries=0
EXACT_WIDGET_FIXTURE_PATH="$EXACT_CLASSIC_WIDGET_FIXTURE_PATH" \
EXPECTED_WIDGET_SHA256="$EXPECTED_CLASSIC_WIDGET_SHA256" \
npx playwright test e2e/widget.issue-canary.spec.ts --project=chromium-issue-canary --workers=1 --retries=0
env:
VERCEL_AUTOMATION_BYPASS_SECRET: ${{ secrets.VERCEL_AUTOMATION_BYPASS_SECRET }}

Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ node_modules/
# Build outputs
dist/
public/widget.js
public/widget.classic.js
public/widget.v*.js
public/versions.json

Expand Down
16 changes: 7 additions & 9 deletions e2e/default-flow-compatibility.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,10 @@ function host(page: Page) {
}

async function prepareContext(context: BrowserContext, runner: Runner, captureHook?: string) {
if (runner === 'private') {
await context.addInitScript(() => {
(window as unknown as { __bugdropDefaultFlowRuntime?: string }).__bugdropDefaultFlowRuntime =
'private';
});
}
await context.addInitScript(selected => {
(window as unknown as { __bugdropDefaultFlowRuntime?: string }).__bugdropDefaultFlowRuntime =
selected;
}, runner);
if (captureHook) await context.addInitScript({ content: captureHook });
}

Expand Down Expand Up @@ -83,9 +81,9 @@ async function expectPairedJourney(
captureHook?: string
): Promise<JourneyResult> {
const fixed = await runJourney(browser, 'fixed', journey, captureHook);
const privateRuntime = await runJourney(browser, 'private', journey, captureHook);
expect(privateRuntime.trace).toEqual(fixed.trace);
expect(privateRuntime.requests).toEqual(fixed.requests);
const flowRuntime = await runJourney(browser, 'private', journey, captureHook);
expect(flowRuntime.trace).toEqual(fixed.trace);
expect(flowRuntime.requests).toEqual(fixed.requests);
return fixed;
}

Expand Down
142 changes: 142 additions & 0 deletions e2e/public-flow.flow-live.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import { expect, type Page } from '@playwright/test';
import {
assertExactPreviewWidgetResponse,
test,
waitForPreviewWidgetResponse,
} from './live-preview-widget';

const expectedWidgetOrigin = process.env.EXPECTED_WIDGET_ORIGIN;
const expectedWidgetSha256 = process.env.EXPECTED_WIDGET_SHA256;
const venuePath = process.env.LIVE_VENUE_PATH || '/';
const bypassSecret = process.env.VERCEL_AUTOMATION_BYPASS_SECRET;

if (bypassSecret) {
test.beforeEach(async ({ context }) => {
await context.route('**/*.vercel.app/**', route =>
route.continue({
headers: {
...route.request().headers(),
'x-vercel-protection-bypass': bypassSecret,
},
})
);
});
}

async function loadFlowWidget(page: Page): Promise<void> {
const response = expectedWidgetOrigin
? waitForPreviewWidgetResponse(page, expectedWidgetOrigin)
: undefined;
await page.goto(venuePath);
await expect
.poll(() => page.evaluate(() => typeof window.BugDrop?.registerFlow))
.toBe('function');
if (response && expectedWidgetSha256) {
await assertExactPreviewWidgetResponse(await response, expectedWidgetSha256);
}
}

test('runs a conditional multi-screen FlowConfig through the exact preview widget', async ({
page,
}) => {
await page.route('**/api/check/**', route =>
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ installed: true, appName: 'neonwatty-bugdrop' }),
})
);
const submissions: Array<Record<string, unknown>> = [];
await page.route('**/api/feedback', route => {
submissions.push(route.request().postDataJSON() as Record<string, unknown>);
return route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
success: true,
issueNumber: 902,
issueUrl: 'https://github.com/mean-weasel/bugdrop-widget-test/issues/902',
isPublic: false,
}),
});
});

await loadFlowWidget(page);
await page.evaluate(() => {
window
.BugDrop!.registerFlow({
configVersion: 1,
id: 'merge-queue-composable-flow',
presentation: { kind: 'modal' },
forms: [
{
id: 'triage',
title: 'Classify your feedback',
fields: [
{
id: 'kind',
type: 'singleChoice',
label: 'Type',
required: true,
options: [
{ value: 'bug', label: 'Bug' },
{ value: 'idea', label: 'Idea' },
],
},
{ id: 'summary', type: 'shortText', label: 'Summary', required: true },
],
},
{
id: 'detail',
title: 'Describe the bug',
fields: [{ id: 'description', type: 'longText', label: 'Steps', required: true }],
},
],
screens: [
{ id: 'intro', type: 'message', title: 'Help us improve' },
{ id: 'triage-screen', type: 'form', form: 'triage' },
{
id: 'detail-screen',
type: 'form',
form: 'detail',
when: { answer: 'triage.kind', equals: 'bug' },
},
{
id: 'screenshot',
type: 'screenshot',
mode: 'optional',
when: { answer: 'triage.kind', equals: 'bug' },
},
],
issue: {
classification: 'bug',
title: '{{triage.summary}}',
sections: [{ heading: 'Steps', answer: 'detail.description' }],
},
})
.open();
});

const host = page.locator('body > [data-bugdrop-flow="merge-queue-composable-flow"]');
await expect(host.getByRole('heading', { name: 'Help us improve' })).toBeVisible();
await host.getByRole('button', { name: 'Continue' }).click();
await host.getByLabel('Bug').click();
await host.getByLabel('Summary').fill('Preview flow failure');
await host.getByRole('button', { name: 'Continue' }).click();
await host.getByLabel('Steps').fill('Open the preview and submit');
await host.getByRole('button', { name: 'Continue' }).click();
await host.getByLabel('Include a screenshot', { exact: true }).uncheck();
await host.getByRole('button', { name: 'Submit' }).click();
await expect(host.getByRole('heading', { name: 'Thanks for your feedback!' })).toBeVisible();

expect(submissions).toHaveLength(1);
expect(submissions[0]).toMatchObject({
repo: 'mean-weasel/bugdrop-widget-test',
title: 'Preview flow failure',
description: '## Steps\n\nOpen the preview and submit',
category: 'bug',
screenshot: null,
attachments: [],
});
expect(submissions[0]?.kind).toBeUndefined();
});
12 changes: 11 additions & 1 deletion playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const baseURL = process.env.PLAYWRIGHT_BASE_URL || 'http://localhost:8787';
const issueCanaryProject = 'chromium-issue-canary';
const liveProjects = [
'chromium-live',
'chromium-flow-live',
'chromium-live-radix',
'chromium-cross-browser-live',
'firefox-cross-browser-live',
Expand Down Expand Up @@ -63,7 +64,7 @@ export default defineConfig({
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
testIgnore: [
/.*\.(?:live|live-radix|cross-browser-live|issue-canary|radix)\.spec\.ts$/,
/.*\.(?:live|flow-live|live-radix|cross-browser-live|issue-canary|radix)\.spec\.ts$/,
/default-flow-production\.spec\.ts$/,
],
},
Expand Down Expand Up @@ -91,6 +92,15 @@ export default defineConfig({
testMatch: liveTestMatch(/.*\.live\.spec\.ts/),
timeout: 60_000,
},
{
name: 'chromium-flow-live',
fullyParallel: false,
use: {
...devices['Desktop Chrome'],
},
testMatch: liveTestMatch(/.*\.flow-live\.spec\.ts/),
timeout: 60_000,
},
{
name: 'chromium-live-radix',
fullyParallel: false,
Expand Down
Loading