Skip to content
Draft
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
21 changes: 18 additions & 3 deletions .github/workflows/lintBuildTest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,15 @@ jobs:
- name: Build
run: npm run build
playwright:
name: Playwright (${{ matrix.browser }})
name: Playwright (${{ matrix.browser }}, shard ${{ matrix.shard }}/3)
timeout-minutes: 20
runs-on: macos-15-xlarge
needs: install
strategy:
fail-fast: false
matrix:
browser: ['chrome', 'firefox', 'safari']
shard: [1, 2, 3]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
Expand Down Expand Up @@ -96,10 +97,24 @@ jobs:
if: steps.playwright-cache.outputs.cache-hit == 'true'
run: npx playwright install-deps
- name: Run Playwright browser tests
run: npx playwright test --project=${{matrix.browser}}
run: npx playwright test --project=${{matrix.browser}} --shard=${{matrix.shard}}/3
- uses: actions/upload-artifact@v4
if: always()
with:
name: test-results-${{ matrix.browser }}
name: test-results-${{ matrix.browser }}-shard-${{ matrix.shard }}
path: test-results/
retention-days: 7
# Single status check for branch protection after all shards complete
playwright-result:
name: Playwright
if: always()
needs: playwright
runs-on: macos-15-xlarge
steps:
- run: |
if [ "${{ needs.playwright.result }}" = "success" ]; then
echo "All Playwright shards passed"
else
echo "Some Playwright shards failed: ${{ needs.playwright.result }}"
exit 1
fi
4 changes: 2 additions & 2 deletions app/msw-mock-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@ export async function startMockAPI() {

// defined in here because it depends on the dynamic import
const interceptAll = http.all('/v1/*', async () => {
// random delay on all requests to simulate a real API
await sleep(randInt(200, 400))
// random delay on all requests to simulate a real API (shorter in e2e)
await sleep(process.env.FAST_MOCK ? randInt(50, 150) : randInt(200, 400))

if (shouldFail(chaos)) {
// special header lets client indicate chaos failures so we don't get confused
Expand Down
10 changes: 5 additions & 5 deletions mock-api/msw/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ export const handlers = makeHandlers({
throw 'Can only enter state importing_from_bulk_write from import_ready'
}

await delay(2000) // slow it down for the tests
await delay(process.env.FAST_MOCK ? 1000 : 2000)

db.diskBulkImportState.set(disk.id, { blocks: {} })
disk.state = { state: 'importing_from_bulk_writes' }
Expand All @@ -239,7 +239,7 @@ export const handlers = makeHandlers({
if (disk.state.state !== 'importing_from_bulk_writes') {
throw 'Can only stop import for disk in state importing_from_bulk_write'
}
await delay(2000) // slow it down for the tests
await delay(process.env.FAST_MOCK ? 1000 : 2000)

db.diskBulkImportState.delete(disk.id)
disk.state = { state: 'import_ready' }
Expand All @@ -249,7 +249,7 @@ export const handlers = makeHandlers({
const disk = lookup.disk({ ...path, ...query })
const diskImport = db.diskBulkImportState.get(disk.id)
if (!diskImport) throw notFoundErr(`disk import for disk '${disk.id}'`)
await delay(1000) // slow it down for the tests
await delay(1000)
// if (Math.random() < 0.01) throw 400
diskImport.blocks[body.offset] = true
return 204
Expand Down Expand Up @@ -1932,7 +1932,7 @@ export const handlers = makeHandlers({
// https://github.com/oxidecomputer/omicron/blob/cf38148d/nexus/src/app/metrics.rs#L154-L179

// timeseries queries are slower than most other queries
await delay(1000)
await delay(process.env.FAST_MOCK ? 400 : 1000)
const data = handleOxqlMetrics(body)

// we use other-project to test certain response cases
Expand All @@ -1955,7 +1955,7 @@ export const handlers = makeHandlers({
async systemTimeseriesQuery({ cookies, body }) {
requireFleetViewer(cookies)
// timeseries queries are slower than most other queries
await delay(1000)
await delay(process.env.FAST_MOCK ? 400 : 1000)
return handleOxqlMetrics(body)
},
siloMetric: handleMetrics,
Expand Down
4 changes: 2 additions & 2 deletions playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export default {
fullyParallel: true,
// default is 5 seconds. somehow playwright really hates async route modules,
// takes a long time to load them. https://playwright.dev/docs/test-timeouts
expect: { timeout: 10_000 },
expect: { timeout: 7000 },
use: {
trace: process.env.CI ? 'on-first-retry' : 'retain-on-failure',
baseURL: 'http://localhost:4009',
Expand Down Expand Up @@ -61,7 +61,7 @@ export default {
],
// use different port so it doesn't conflict with local dev server
webServer: {
command: 'npm run start:msw -- --port 4009',
command: 'FAST_MOCK=1 npm run start:msw -- --port 4009',
port: 4009,
},
} satisfies PlaywrightTestConfig
8 changes: 4 additions & 4 deletions test/e2e/scroll-restore.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ test('scroll restore', async ({ page }) => {
await expectScrollTop(page, 143)

// sleep required to get the scroll position to stick
await sleep(1000)
await sleep(500)
await scrollTo(page, 190)
await sleep(1000)
await sleep(500)

// go forward to snapshots, now scroll it
await page.goForward()
Expand All @@ -42,7 +42,7 @@ test('scroll restore', async ({ page }) => {
// catch the 30 scroll position. This became necessary with RR v7's use of
// startTransition. Extra oddly, with a value of 500 it passes rarely, but
// with 1000 it passes every time.
await sleep(1000)
await sleep(500)

// new nav to disks
await page.getByRole('link', { name: 'Disks' }).click()
Expand All @@ -63,7 +63,7 @@ test('scroll restore', async ({ page }) => {
// back again to disks, newer scroll value is restored
await page.goBack()
await expect(page).toHaveURL('/projects/mock-project/disks')
await sleep(1000)
await sleep(500)
await expectScrollTop(page, 190)

// forward again to newest disks history entry, scroll remains 0
Expand Down
2 changes: 1 addition & 1 deletion test/e2e/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ export async function closeToast(page: Page) {
// we don't have time to close the first one. Without first(), this errors out
// because there are two toasts.
await page.getByRole('button', { name: 'Dismiss notification' }).first().click()
await sleep(1000)
await sleep(500)
}

/**
Expand Down
2 changes: 2 additions & 0 deletions vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ export default defineConfig(({ mode }) => ({
'process.env.SHA': JSON.stringify(process.env.SHA),
// used by MSW — number for % likelihood of API request failure (decimals allowed)
'process.env.CHAOS': JSON.stringify(mode !== 'production' && process.env.CHAOS),
// skip artificial delays in mock API handlers (used by e2e tests)
'process.env.FAST_MOCK': JSON.stringify(!!process.env.FAST_MOCK),
},
plugins: [
tailwindcss(),
Expand Down
Loading