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
150 changes: 110 additions & 40 deletions .github/workflows/e2e-tests-split.yml
Original file line number Diff line number Diff line change
Expand Up @@ -325,11 +325,18 @@ jobs:

- name: Install Playwright Chromium
run: |
set -uo pipefail
echo "📦 Installing Chromium..."
npx playwright install --with-deps chromium
EXIT_CODE=$?
echo "✅ Install command completed (exit code: $EXIT_CODE)"
exit "$EXIT_CODE"
for attempt in 1 2 3; do
timeout 10m npx playwright install --with-deps chromium && break
if [ "$attempt" -lt 3 ]; then
echo "Attempt ${attempt}/3 failed; retrying in 15s..." >&2
sleep 15
else
echo "ERROR: Playwright chromium install failed after 3 attempts" >&2
exit 1
fi
done

- name: Run Chromium Security Enforcement Tests
run: |
Expand Down Expand Up @@ -560,19 +567,33 @@ jobs:

- name: Install Playwright Chromium (required by security-tests dependency)
run: |
set -uo pipefail
echo "📦 Installing Chromium (required by security-tests dependency)..."
npx playwright install --with-deps chromium
EXIT_CODE=$?
echo "✅ Install command completed (exit code: $EXIT_CODE)"
exit "$EXIT_CODE"
for attempt in 1 2 3; do
timeout 10m npx playwright install --with-deps chromium && break
if [ "$attempt" -lt 3 ]; then
echo "Attempt ${attempt}/3 failed; retrying in 15s..." >&2
sleep 15
else
echo "ERROR: Playwright chromium install failed after 3 attempts" >&2
exit 1
fi
done

- name: Install Playwright Firefox
run: |
set -uo pipefail
echo "📦 Installing Firefox..."
npx playwright install --with-deps firefox
EXIT_CODE=$?
echo "✅ Install command completed (exit code: $EXIT_CODE)"
exit "$EXIT_CODE"
for attempt in 1 2 3; do
timeout 10m npx playwright install --with-deps firefox && break
if [ "$attempt" -lt 3 ]; then
echo "Attempt ${attempt}/3 failed; retrying in 15s..." >&2
sleep 15
else
echo "ERROR: Playwright firefox install failed after 3 attempts" >&2
exit 1
fi
done

- name: Run Firefox Security Enforcement Tests
run: |
Expand Down Expand Up @@ -806,19 +827,33 @@ jobs:

- name: Install Playwright Chromium (required by security-tests dependency)
run: |
set -uo pipefail
echo "📦 Installing Chromium (required by security-tests dependency)..."
npx playwright install --with-deps chromium
EXIT_CODE=$?
echo "✅ Install command completed (exit code: $EXIT_CODE)"
exit "$EXIT_CODE"
for attempt in 1 2 3; do
timeout 10m npx playwright install --with-deps chromium && break
if [ "$attempt" -lt 3 ]; then
echo "Attempt ${attempt}/3 failed; retrying in 15s..." >&2
sleep 15
else
echo "ERROR: Playwright chromium install failed after 3 attempts" >&2
exit 1
fi
done

- name: Install Playwright WebKit
run: |
set -uo pipefail
echo "📦 Installing WebKit..."
npx playwright install --with-deps webkit
EXIT_CODE=$?
echo "✅ Install command completed (exit code: $EXIT_CODE)"
exit "$EXIT_CODE"
for attempt in 1 2 3; do
timeout 10m npx playwright install --with-deps webkit && break
if [ "$attempt" -lt 3 ]; then
echo "Attempt ${attempt}/3 failed; retrying in 15s..." >&2
sleep 15
else
echo "ERROR: Playwright webkit install failed after 3 attempts" >&2
exit 1
fi
done

- name: Run WebKit Security Enforcement Tests
run: |
Expand Down Expand Up @@ -1079,11 +1114,18 @@ jobs:

- name: Install Playwright Chromium
run: |
set -uo pipefail
echo "📦 Installing Chromium..."
npx playwright install --with-deps chromium
EXIT_CODE=$?
echo "✅ Install command completed (exit code: $EXIT_CODE)"
exit "$EXIT_CODE"
for attempt in 1 2 3; do
timeout 10m npx playwright install --with-deps chromium && break
if [ "$attempt" -lt 3 ]; then
echo "Attempt ${attempt}/3 failed; retrying in 15s..." >&2
sleep 15
else
echo "ERROR: Playwright chromium install failed after 3 attempts" >&2
exit 1
fi
done

- name: Run Chromium Non-Security Tests (Shard ${{ matrix.shard }}/${{ matrix.total-shards }})
run: |
Expand Down Expand Up @@ -1317,19 +1359,33 @@ jobs:

- name: Install Playwright Chromium (required by security-tests dependency)
run: |
set -uo pipefail
echo "📦 Installing Chromium (required by security-tests dependency)..."
npx playwright install --with-deps chromium
EXIT_CODE=$?
echo "✅ Install command completed (exit code: $EXIT_CODE)"
exit "$EXIT_CODE"
for attempt in 1 2 3; do
timeout 10m npx playwright install --with-deps chromium && break
if [ "$attempt" -lt 3 ]; then
echo "Attempt ${attempt}/3 failed; retrying in 15s..." >&2
sleep 15
else
echo "ERROR: Playwright chromium install failed after 3 attempts" >&2
exit 1
fi
done

- name: Install Playwright Firefox
run: |
set -uo pipefail
echo "📦 Installing Firefox..."
npx playwright install --with-deps firefox
EXIT_CODE=$?
echo "✅ Install command completed (exit code: $EXIT_CODE)"
exit "$EXIT_CODE"
for attempt in 1 2 3; do
timeout 10m npx playwright install --with-deps firefox && break
if [ "$attempt" -lt 3 ]; then
echo "Attempt ${attempt}/3 failed; retrying in 15s..." >&2
sleep 15
else
echo "ERROR: Playwright firefox install failed after 3 attempts" >&2
exit 1
fi
done

- name: Run Firefox Non-Security Tests (Shard ${{ matrix.shard }}/${{ matrix.total-shards }})
run: |
Expand Down Expand Up @@ -1563,19 +1619,33 @@ jobs:

- name: Install Playwright Chromium (required by security-tests dependency)
run: |
set -uo pipefail
echo "📦 Installing Chromium (required by security-tests dependency)..."
npx playwright install --with-deps chromium
EXIT_CODE=$?
echo "✅ Install command completed (exit code: $EXIT_CODE)"
exit "$EXIT_CODE"
for attempt in 1 2 3; do
timeout 10m npx playwright install --with-deps chromium && break
if [ "$attempt" -lt 3 ]; then
echo "Attempt ${attempt}/3 failed; retrying in 15s..." >&2
sleep 15
else
echo "ERROR: Playwright chromium install failed after 3 attempts" >&2
exit 1
fi
done

- name: Install Playwright WebKit
run: |
set -uo pipefail
echo "📦 Installing WebKit..."
npx playwright install --with-deps webkit
EXIT_CODE=$?
echo "✅ Install command completed (exit code: $EXIT_CODE)"
exit "$EXIT_CODE"
for attempt in 1 2 3; do
timeout 10m npx playwright install --with-deps webkit && break
if [ "$attempt" -lt 3 ]; then
echo "Attempt ${attempt}/3 failed; retrying in 15s..." >&2
sleep 15
else
echo "ERROR: Playwright webkit install failed after 3 attempts" >&2
exit 1
fi
done

- name: Run WebKit Non-Security Tests (Shard ${{ matrix.shard }}/${{ matrix.total-shards }})
run: |
Expand Down
11 changes: 10 additions & 1 deletion frontend/src/components/ui/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,26 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
className,
type,
disabled,
id,
...props
},
ref
) => {
const [showPassword, setShowPassword] = React.useState(false)
const isPassword = type === 'password'
// Auto-generate a stable id when the caller doesn't supply one, so the
// label is always programmatically associated with its control (WCAG
// 1.3.1 / 3.3.2). `id` is destructured out explicitly above — not left
// inside `...props` — so this fallback can't be silently overridden by
// a stray `id: undefined` reaching the spread below in prop order.
const generatedId = React.useId()
const inputId = id ?? generatedId

return (
<div className="w-full">
{label && (
<label
htmlFor={props.id}
htmlFor={inputId}
className="block text-sm font-medium text-content-secondary mb-1.5"
>
{label}
Expand All @@ -49,6 +57,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
)}
<input
ref={ref}
id={inputId}
type={isPassword ? (showPassword ? 'text' : 'password') : type}
disabled={disabled}
aria-describedby={error && errorTestId ? errorTestId : undefined}
Expand Down
15 changes: 15 additions & 0 deletions frontend/src/components/ui/__tests__/Input.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,21 @@ describe('Input', () => {
expect(label).toHaveAttribute('for', 'email-input')
})

it('associates label with input via auto-generated id when no id is passed', () => {
render(<Input label="Email" />)

const input = screen.getByLabelText('Email')
expect(input).toBeInTheDocument()
expect(input.tagName).toBe('INPUT')
})

it('uses the explicit id when provided, not the generated one', () => {
render(<Input label="Email" id="custom-email-id" />)

const input = screen.getByLabelText('Email')
expect(input).toHaveAttribute('id', 'custom-email-id')
})

it('renders with error state and message', () => {
render(
<Input
Expand Down
17 changes: 15 additions & 2 deletions tests/utils/wait-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1330,7 +1330,18 @@ export async function gotoTolerant(
): Promise<void> {
const { timeout = 15000 } = options;

await page.goto(url, { waitUntil: 'commit', timeout }).catch((error: unknown) => {
// waitUntil: 'domcontentloaded', NOT 'commit' — see commit 7503c01a and
// docs/reports/qa_report_2026-07-26_shard4-reload-hang.md. 'commit' fires
// on Firefox's earliest possible navigation signal, which this repo's own
// trace evidence (docs/plans/current_spec.md, PR #1259 RCA) shows Firefox
// can fail to emit at all for some goto()/reload() calls, leaving
// Playwright's frame-navigation tracker stuck and blocking every
// subsequent locator-based wait on the page for a second full timeout.
// 'domcontentloaded' is the value already used by 100+ other call sites
// in this suite (including tests/settings/navigation-settle-regression.spec.ts,
// which codifies this exact failure shape) and is the value this helper's
// predecessor code used before it was silently dropped during extraction.
await page.goto(url, { waitUntil: 'domcontentloaded', timeout }).catch((error: unknown) => {
if (!isExpectedNavigationRace(error)) {
throw error;
}
Expand All @@ -1355,7 +1366,9 @@ export async function reloadTolerant(
): Promise<void> {
const { timeout = 15000 } = options;

await page.reload({ waitUntil: 'commit', timeout }).catch((error: unknown) => {
// See gotoTolerant's comment above — same 'commit' → 'domcontentloaded'
// fix, for the same reason.
await page.reload({ waitUntil: 'domcontentloaded', timeout }).catch((error: unknown) => {
if (!isExpectedNavigationRace(error)) {
throw error;
}
Expand Down
Loading