mirror of
https://github.com/stablyai/orca.git
synced 2026-10-30 16:47:34 +00:00
fix(e2e): keep dedicated specs out of general CI shard plan (#28161)
- Specs with their own CI jobs were also being assigned to general E2E shards, so they could run twice or in the wrong job. - planE2e now drops dedicated specs before balancing and fails if no general specs remain, instead of producing an empty plan. - The mobile subscribe spec builds a paired runtime connection from the pairing offer before sending requests, matching the updated remote runtime helper signatures.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { readFileSync, writeFileSync } from 'node:fs'
|
||||
import { join } from 'node:path'
|
||||
import { pathToFileURL } from 'node:url'
|
||||
import { isDedicatedE2eSpec } from './ci-e2e-job-selection.mjs'
|
||||
import {
|
||||
balanceFiles,
|
||||
compareIds,
|
||||
@@ -44,7 +45,14 @@ export function discoverE2eFiles(report) {
|
||||
}
|
||||
|
||||
export function planE2e(report, count, baseline) {
|
||||
const testsByFile = discoverE2eFiles(report)
|
||||
const testsByFile = Object.fromEntries(
|
||||
Object.entries(discoverE2eFiles(report)).filter(
|
||||
([file]) => !isDedicatedE2eSpec(`tests/e2e/${file}`)
|
||||
)
|
||||
)
|
||||
if (!Object.keys(testsByFile).length) {
|
||||
throw new Error('Playwright discovered no general E2E tests')
|
||||
}
|
||||
const timings = Object.fromEntries(
|
||||
Object.entries(baseline.timings).map(([file, duration]) => [
|
||||
file.replace(/^tests\/e2e\//, ''),
|
||||
|
||||
@@ -4,7 +4,7 @@ import { join, resolve } from 'node:path'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import RuntimeSequencer from './vitest-runtime-sequencer.mjs'
|
||||
import { balanceFiles } from './ci-shard-assignment.mjs'
|
||||
import { discoverE2eFiles, planE2e } from './ci-e2e-shard-plan.mjs'
|
||||
import { discoverE2eFiles, planE2e, verifyE2eSelection } from './ci-e2e-shard-plan.mjs'
|
||||
import { parseTimingLog } from './ci-shard-timing-import.mjs'
|
||||
import TimingSequencer from './ci-unit-sequencer.mjs'
|
||||
import { UNIT_INCLUDE, UNIT_EXCLUDE } from './ci-unit-files.mjs'
|
||||
@@ -145,6 +145,39 @@ describe('timing-weighted shard selection', () => {
|
||||
).toThrow('Duplicate')
|
||||
})
|
||||
|
||||
it('plans only general E2E specs and rejects unexpected dedicated selections', () => {
|
||||
const spec = (id, file) => ({ id, file, tests: [{ projectName: 'electron-headless' }] })
|
||||
const report = {
|
||||
suites: [
|
||||
{
|
||||
specs: [
|
||||
spec('general', 'general.spec.ts'),
|
||||
spec('layout', 'workspace-layout-oracle-headless.spec.ts'),
|
||||
spec('ssh', 'ssh-orcad-terminal.spec.ts')
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
expect(Object.keys(discoverE2eFiles(report))).toEqual([
|
||||
'general.spec.ts',
|
||||
'ssh-orcad-terminal.spec.ts',
|
||||
'workspace-layout-oracle-headless.spec.ts'
|
||||
])
|
||||
const assignment = { ...planE2e(report, 1, { timings: {} }), selectedShard: 1 }
|
||||
expect(assignment.shards[0].files).toEqual(['general.spec.ts'])
|
||||
expect(Object.keys(assignment.testsByFile)).toEqual(['general.spec.ts'])
|
||||
const general = report.suites[0].specs[0]
|
||||
expect(() => verifyE2eSelection(assignment, { suites: [{ specs: [general] }] })).not.toThrow()
|
||||
for (const dedicated of report.suites[0].specs.slice(1)) {
|
||||
expect(() =>
|
||||
verifyE2eSelection(assignment, { suites: [{ specs: [general, dedicated] }] })
|
||||
).toThrow('differs')
|
||||
}
|
||||
expect(() =>
|
||||
planE2e({ suites: [{ specs: report.suites[0].specs.slice(1) }] }, 1, { timings: {} })
|
||||
).toThrow('no general E2E tests')
|
||||
})
|
||||
|
||||
it('imports ANSI unit timings and E2E failures without counting headful reruns', () => {
|
||||
const parsed = parseTimingLog(
|
||||
[
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
import { ensureTerminalVisible, waitForSessionReady } from './helpers/store'
|
||||
import { createRuntimeDesktopPairingOffer } from './helpers/paired-electron-client'
|
||||
import { decodePairingOffer } from '../../src/shared/pairing'
|
||||
import { pairedRuntimeConnectionFromOffer } from '../../src/shared/paired-runtime-connection'
|
||||
import {
|
||||
sendRemoteRuntimeRequest,
|
||||
subscribeRemoteRuntimeRequest
|
||||
@@ -132,6 +133,7 @@ test('phone subscribe to an idle relaunched terminal behind another tab fits the
|
||||
|
||||
const offer = await createRuntimeDesktopPairingOffer(second.page)
|
||||
const pairing = decodePairingOffer(offer.pairingUrl)
|
||||
const connection = pairedRuntimeConnectionFromOffer(pairing)
|
||||
let handle: string | undefined
|
||||
// The new tab's PTY spawn refuses terminal.list until its pane binds.
|
||||
await expect
|
||||
@@ -139,7 +141,7 @@ test('phone subscribe to an idle relaunched terminal behind another tab fits the
|
||||
async () => {
|
||||
const list = await sendRemoteRuntimeRequest<{
|
||||
terminals: { handle: string; ptyId: string | null }[]
|
||||
}>(pairing, 'terminal.list', {}, 15_000)
|
||||
}>(connection, 'terminal.list', {}, 15_000)
|
||||
if (!list.ok) {
|
||||
return JSON.stringify(list)
|
||||
}
|
||||
@@ -152,7 +154,7 @@ test('phone subscribe to an idle relaunched terminal behind another tab fits the
|
||||
|
||||
const seen: { snapshot: { cols: number; rows: number } | null } = { snapshot: null }
|
||||
const subscription = await subscribeRemoteRuntimeRequest(
|
||||
pairing,
|
||||
connection,
|
||||
'terminal.subscribe',
|
||||
{
|
||||
terminal: handle,
|
||||
|
||||
Reference in New Issue
Block a user