Skip to content

Commit bb214da

Browse files
[Issue #8942] Split staging E2E workflow: sharded no-auth, single-worker auth (#8965)
## Summary Work for : Task #8942 ## Changes proposed - Tests that require Login.gov authentication now contain logic to run only against Chrome when targeting staging. - The staging workflow has been split into two jobs: - e2e-tests-no-auth-sharded : Runs in parallel shards similar to local E2E runs. - e2e-tests-auth-single-worker : Runs auth-related tests in a single shard to avoid parallel Login.gov sessions. ## Context for reviewers Due to Staging Login.gov limitations - authentication tests cannot run in parallel. Splitting the workflow ensures non-auth tests still benefit from parallelization while auth tests run safely in a single worker. During this PR, we noticed that - Even with serially run tests, there were few more challenges with Login.gov OTP codes, this PR mainly addresses the fix for these challenges + extra tuning on other tests. - Login.gov OTPs are valid for 30 seconds and single-use. In longer test runs or in serially run tests: - By the time a Test in series reaches the MFA step, the generated code becomes invalid before login.gov processes it or - If a test was faster the same code used in previous test login may be attempted again by another test (as the OTP was still valid), which login.gov rejects even if it is still within the same window. This PR ensures a fresh OTP is generated and handles retry logic if the code is rejected, making the login flow more stable for the E2E test run. ## Credit Tagging approach inspired by @doug-s-nava's PR #8960 , where tags are passed via the test options object instead of embedding them in the test name.
1 parent 46ce87d commit bb214da

22 files changed

+569
-255
lines changed

.github/actions/e2e/action.yml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ inputs:
4141
node_options:
4242
description: "options to pass to node"
4343
default: "--dns-result-order=ipv4first"
44+
workers:
45+
description: "number of workers to use for test run"
46+
default: ""
4447
outputs:
4548
artifact-id:
4649
description: "artifact ids for uploaded test reports"
@@ -87,14 +90,14 @@ runs:
8790
TOTAL_SHARDS: ${{ inputs.total_shards }}
8891
CURRENT_SHARD: ${{ inputs.current_shard }}
8992
PLAYWRIGHT_TARGET_ENV: ${{ inputs.target }}
93+
PLAYWRIGHT_WORKERS: ${{ inputs.workers }}
9094
STAGING_TEST_USER_EMAIL: ${{ inputs.staging_test_user_email }}
9195
STAGING_TEST_USER_PASSWORD: ${{ inputs.staging_test_user_password }}
9296
STAGING_TEST_USER_MFA_KEY: ${{ inputs.staging_test_user_mfa_key }}
9397
run: |
9498
npm run test:e2e
9599
shell: bash
96100

97-
98101
- name: Run e2e tests for ${{ inputs.playwright_tags }} (Shard ${{ inputs.current_shard }}/${{ inputs.total_shards }})
99102
if: ${{ inputs.playwright_tags != '' }}
100103
working-directory: ./frontend
@@ -103,6 +106,7 @@ runs:
103106
TOTAL_SHARDS: ${{ inputs.total_shards }}
104107
CURRENT_SHARD: ${{ inputs.current_shard }}
105108
PLAYWRIGHT_TARGET_ENV: ${{ inputs.target }}
109+
PLAYWRIGHT_WORKERS: ${{ inputs.workers }}
106110
STAGING_TEST_USER_EMAIL: ${{ inputs.staging_test_user_email }}
107111
STAGING_TEST_USER_PASSWORD: ${{ inputs.staging_test_user_password }}
108112
STAGING_TEST_USER_MFA_KEY: ${{ inputs.staging_test_user_mfa_key }}
@@ -111,7 +115,7 @@ runs:
111115
shell: bash
112116

113117
- name: Debug logging on failure
114-
if: ${{ failure() && inputs.api_logs == 'true' }}
118+
if: ${{ failure() && inputs.api_logs == 'true' }}
115119
working-directory: ./frontend
116120
run: |
117121
cd ../api

.github/workflows/e2e-create-report.yml

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,37 +8,31 @@ on:
88
artifact-ids:
99
description: "comma separated string of test report artifact ids to download"
1010
type: string
11-
1211
env:
1312
NODE_VERSION: 24
1413
LOCKFILE_PATH: ./frontend/package-lock.json
1514
PACKAGE_MANAGER: npm
1615
NODE_OPTIONS: --dns-result-order=ipv4first
17-
1816
defaults:
1917
run:
2018
working-directory: ./frontend
21-
2219
jobs:
2320
e2e-create-report:
2421
continue-on-error: true
2522
runs-on: ubuntu-22.04
2623
steps:
2724
- name: Checkout code
2825
uses: actions/checkout@v6
29-
3026
- name: Setup Node.js
3127
uses: actions/setup-node@v6
3228
with:
3329
node-version: ${{ env.NODE_VERSION }}
3430
cache: ${{ env.PACKAGE_MANAGER }}
3531
cache-dependency-path: ${{ env.LOCKFILE_PATH }}
36-
3732
- name: Install Playwright Dependencies
3833
run: |
3934
npm ci
4035
npx playwright install --with-deps
41-
4236
- name: Download All Blob Reports
4337
uses: actions/download-artifact@v7
4438
with:
@@ -47,20 +41,16 @@ jobs:
4741
run-id: ${{ inputs.run_id }}
4842
pattern: blob-report-shard-*
4943
merge-multiple: true
50-
5144
- name: Verify Downloaded Artifacts
5245
run: |
5346
echo "Contents of all-blob-reports after download:"
5447
ls -R all-blob-reports*
55-
5648
- name: Merge Blob Reports into HTML
5749
run: npx playwright merge-reports --reporter html ./all-blob-reports
58-
5950
- name: Verify Downloaded Artifacts
6051
run: |
6152
echo "Contents of all-blob-reports after download:"
6253
ls -R playwright*
63-
6454
- name: Upload Merged HTML Report
6555
uses: actions/upload-artifact@v6
6656
with:

.github/workflows/e2e-staging.yml

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,39 +20,29 @@ on:
2020
description: "pipe separated list of @tags denoting groups of tests to run"
2121
required: false
2222
type: string
23-
2423
concurrency:
2524
group: ${{ github.workflow }}-${{ github.ref }}
2625
cancel-in-progress: true
27-
2826
jobs:
2927
e2e-tests-deployed:
3028
runs-on: ubuntu-22.04
31-
strategy:
32-
fail-fast: false
33-
matrix:
34-
shard: [1, 2, 3, 4]
35-
total_shards: [4]
36-
3729
steps:
3830
- name: Checkout repository
3931
uses: actions/checkout@v6
40-
4132
- name: Set target
4233
run: if [[ -z "${{ inputs.target }}" ]]; then echo "defaulted_target=staging" >> "$GITHUB_ENV"; else echo "defaulted_target=${{ inputs.target }}" >> "$GITHUB_ENV"; fi
43-
4434
- name: Run E2E tests
4535
uses: ./.github/actions/e2e
4636
with:
4737
version: ${{ inputs.version || github.ref }}
4838
target: ${{ env.defaulted_target }}
49-
total_shards: ${{ matrix.total_shards }}
50-
current_shard: ${{ matrix.shard }}
39+
total_shards: 1
40+
current_shard: 1
41+
workers: 1
5142
playwright_tags: ${{ inputs.playwright_tags }}
5243
staging_test_user_email: ${{ secrets.STAGING_TEST_USER_EMAIL }}
5344
staging_test_user_password: ${{ secrets.STAGING_TEST_USER_PASSWORD }}
5445
staging_test_user_mfa_key: ${{ secrets.STAGING_TEST_USER_MFA_KEY }}
55-
5646
create-report:
5747
name: Create Merged Test Report
5848
if: ${{ !cancelled() }}
@@ -61,7 +51,6 @@ jobs:
6151
secrets: inherit
6252
with:
6353
run_id: ${{ github.run_id }}
64-
6554
send-slack-notification:
6655
if: failure()
6756
needs: [create-report]

frontend/src/components/application/ApplicationFormsTable.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,10 @@ const FormLink = ({
316316
{formName && (
317317
<Link
318318
className="text-bold"
319+
// Added stable test attributes for reliable E2E targeting of form links.
320+
// Enables Playwright to select the correct link regardless of text or layout changes.
321+
data-testid="application-form-link"
322+
data-form-id={formId}
319323
href={`/applications/${applicationId}/form/${appFormId}`}
320324
>
321325
{formName}

frontend/tests/e2e/apply/failure-path-sf424b.spec.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { openForm } from "tests/e2e/utils/forms/form-navigation-utils";
1212
import { saveForm } from "tests/e2e/utils/forms/save-form-utils";
1313
import { verifyFormStatusAfterSave } from "tests/e2e/utils/forms/verify-form-status-utils";
1414

15-
const { testOrgLabel } = playwrightEnv;
15+
const { testOrgLabel, targetEnv } = playwrightEnv;
1616
const OPPORTUNITY_ID = "f7a1c2b3-4d5e-6789-8abc-1234567890ab"; // TEST-APPLY-ORG-IND-ON01
1717
const OPPORTUNITY_URL = `/opportunity/${OPPORTUNITY_ID}`;
1818

@@ -24,6 +24,16 @@ const sf424bErrors = [
2424
},
2525
];
2626

27+
// Skip non-Chrome browsers in staging
28+
test.beforeEach(({ page: _ }, testInfo) => {
29+
if (targetEnv === "staging") {
30+
test.skip(
31+
testInfo.project.name !== "Chrome",
32+
"Staging MFA login is limited to Chrome to avoid OTP rate-limiting",
33+
);
34+
}
35+
});
36+
2737
test("SF-424B error validation - required fields and inline errors", async ({
2838
page,
2939
context,

frontend/tests/e2e/apply/fill-sflll-form.spec.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,22 @@ import { fillForm } from "tests/e2e/utils/forms/general-forms-filling";
1111
import {
1212
clearPageState,
1313
ensurePageClosed,
14-
} from "tests/e2e/utils/lifecycle-helpers";
14+
} from "tests/e2e/utils/lifecycle-utils";
1515

16-
const { baseUrl, testOrgLabel, opportunityId } = playwrightEnv;
16+
const { baseUrl, testOrgLabel, opportunityId, targetEnv } = playwrightEnv;
1717
const OPPORTUNITY_URL = `/opportunity/${opportunityId}`;
1818

1919
test.describe("fill SF-LLL Form", () => {
20+
// Skip non-Chrome browsers in staging
21+
test.beforeEach(({ page: _ }, testInfo) => {
22+
if (targetEnv === "staging") {
23+
test.skip(
24+
testInfo.project.name !== "Chrome",
25+
"Staging MFA login is limited to Chrome to avoid OTP rate-limiting",
26+
);
27+
}
28+
});
29+
2030
test.beforeEach(async ({ page, context }, testInfo) => {
2131
const isMobile = testInfo.project.name.match(/[Mm]obile/);
2232
await authenticateE2eUser(page, context, !!isMobile);

frontend/tests/e2e/apply/happy-path-application-submission.spec.ts

Lines changed: 34 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,20 @@ import { selectFormInclusionOption } from "tests/e2e/utils/forms/select-form-inc
1717
import { verifyFormStatusAfterSave } from "tests/e2e/utils/forms/verify-form-status-utils";
1818
import { submitApplicationAndVerify } from "tests/e2e/utils/submit-application-utils";
1919

20-
const { testOrgLabel } = playwrightEnv;
20+
const { testOrgLabel, targetEnv } = playwrightEnv;
2121
const OPPORTUNITY_ID = "f7a1c2b3-4d5e-6789-8abc-1234567890ab"; // TEST-APPLY-ORG-IND-ON01
2222
const OPPORTUNITY_URL = `/opportunity/${OPPORTUNITY_ID}`;
2323

24+
// Skip non-Chrome browsers in staging
25+
test.beforeEach(({ page: _ }, testInfo) => {
26+
if (targetEnv === "staging") {
27+
test.skip(
28+
testInfo.project.name !== "Chrome",
29+
"Staging MFA login is limited to Chrome to avoid OTP rate-limiting",
30+
);
31+
}
32+
});
33+
2434
test("Application submission happy path - application with required SF424B and unsubmitted conditional SFLLL", async ({
2535
page,
2636
context,
@@ -35,33 +45,32 @@ test("Application submission happy path - application with required SF424B and u
3545
await createApplication(page, OPPORTUNITY_URL, testOrgLabel);
3646
const applicationUrl = page.url();
3747

38-
if (await openForm(page, SF424B_FORM_MATCHER)) {
39-
// Fill SF-424B form fields using helper
40-
await fillSf424bForm(page, "TESTER", testOrgLabel);
48+
if (!(await openForm(page, SF424B_FORM_MATCHER))) {
49+
throw new Error(
50+
"Could not find or open SF-424B form link on the application forms page",
51+
);
52+
}
53+
54+
// Fill SF-424B form fields using helper
55+
await fillSf424bForm(page, "TESTER", testOrgLabel);
4156

42-
// Save the form using helper
43-
await saveForm(page);
57+
// Save the form using helper
58+
await saveForm(page);
4459

45-
// Verify form status after save
46-
await verifyFormStatusAfterSave(
47-
page,
48-
"complete",
49-
"SF-424B",
50-
applicationUrl,
51-
);
60+
// Verify form status after save
61+
await verifyFormStatusAfterSave(page, "complete", "SF-424B", applicationUrl);
5262

53-
// Extra wait for page to fully render forms table after navigation
54-
await page.waitForTimeout(10000);
63+
// Extra wait for page to fully render forms table after navigation
64+
await page.waitForTimeout(10000);
5565

56-
// Select 'No' for including SF-LLL form in submission
57-
await selectFormInclusionOption(
58-
page,
59-
"Disclosure of Lobbying Activities (SF-LLL)",
60-
"No",
61-
);
66+
// Select 'No' for including SF-LLL form in submission
67+
await selectFormInclusionOption(
68+
page,
69+
"Disclosure of Lobbying Activities (SF-LLL)",
70+
"No",
71+
);
6272

63-
// Submit the application and verify success
64-
await submitApplicationAndVerify(page);
65-
// Application ID is now available in appId variable for further use if needed
66-
}
73+
// Submit the application and verify success
74+
await submitApplicationAndVerify(page);
75+
// Application ID is now available in appId variable for further use if needed
6776
});

0 commit comments

Comments
 (0)