mirror of
https://github.com/stablyai/orca.git
synced 2026-09-21 16:02:20 +00:00
* build(macos): run native module builds concurrently
* fix(build): terminate sibling native builds when one fails
Address coderabbit review: concurrent builds kept writing native
artifacts after a sibling reported failure. Track spawned children,
kill remaining siblings on first nonzero exit, and forward SIGINT/
SIGTERM to all children.
* fix(build): process-group teardown and prefixed output for parallel native builds
Address second coderabbit round:
- Detached process groups + negative-pid kill so SIGTERM reaches swift/
swiftc descendants, not just the direct pnpm child (they could keep
writing artifacts after fail-fast)
- Signal handlers preserve the received signal (SIGINT no longer becomes
SIGTERM for children) and are removed before re-raising, so the parent
actually dies instead of looping through terminateAll
- runPnpmScript settles only on close, never on error alone, so
Promise.all cannot exit while children are still running
- Per-module output prefixes ([computer]/[keyboard-layout]/[notification-
status]) match what the PR description always claimed; interleaved
swiftc errors are now attributable
- Windows path untouched (early return before any of this runs)
execa/p-limit were considered and rejected: no new runtime deps for a
build script, and detached process groups give strictly stronger cleanup
than execa's direct-child kill.
* fix(build): memoized handler removal and external-vs-sibling signal split
Second-round coderabbit findings on 24392a0:
- Registration now uses the memoized handlerFor() instances so
removeListener actually removes them (inline arrows were never
registered, so the parent looped through terminateAll and hung)
- externalSignal is set only by the parent's own signal handlers; a
sibling's fail-fast SIGTERM no longer masquerades as an external
signal, so settle() resolves Promise.all with the failing module's
exit code instead of leaving top-level await unsettled (exit 13)
- Also fixes a TDZ crash: handlerFor() was invoked at registration time
before the signalHandlers const initialized
Verified: sibling fail-fast resolves failer=7 with no survivors;
external SIGINT kills children then the parent exits 130; real
concurrent macOS build green.
* Wait for native build cancellation before exiting
* Clean up native builds when output streams fail
* fix: bound native build waits, forward SIGHUP, honour output backpressure
- Bound the per-child close wait: two seconds after a child exits, reap
its process group and destroy its pipes so a descendant that inherited
stdout/stderr cannot hang `pnpm build:native` forever.
- Handle SIGHUP alongside SIGINT/SIGTERM so a terminal hangup reaches the
detached compiler sessions instead of orphaning them.
- Pause a compiler's output stream when the launcher's stdout/stderr
reports backpressure and resume on drain, so prefixed output no longer
buffers without bound.
- Run build-native-for-platform.test.mjs in the computer-e2e
mac-native-owner-smoke PR job and trigger that workflow on launcher
changes; the tests are darwin-only and no other PR job runs on macOS.
- Report the first failing child's status: re-raise its signal, or use
its exit code instead of Math.max over cancelled siblings.
* fix(native-build): keep output when reap timer overlaps backpressure; fail on ignored re-raised signal
The descendant reap timer started on every child 'exit' and fired even when
'close' was late only because the launcher paused the pipe for its own stdout
backpressure, destroying pipes with compiler output still queued. Arm the
countdown only while the pipes are actually draining: clear it on 'pause' and
re-arm on 'resume' after exit. Write the reap notice to stderr since stdout
is the stream that may be blocked.
Re-raising a child's fatal signal is a no-op when Node ignores it (SIGPIPE),
so set a non-zero exit code first; a failed build no longer exits 0.
Tests: stall the launcher's stdout consumer past the reap timeout and assert
every kernel-accepted compiler line still arrives; kill the computer build
with SIGPIPE and assert the launcher exits 1.
---------
Co-authored-by: m4air <m4air@Mac.localdomain>
Co-authored-by: Neil <neil@stably.ai>
400 lines
17 KiB
JavaScript
400 lines
17 KiB
JavaScript
import { readFileSync } from 'node:fs'
|
|
import { join, resolve } from 'node:path'
|
|
import { describe, expect, it } from 'vitest'
|
|
import { parse } from 'yaml'
|
|
|
|
const projectDir = resolve(import.meta.dirname, '../..')
|
|
|
|
describe('computer-use e2e workflow', () => {
|
|
it('cancels superseded pull request runs without cancelling scheduled runs', () => {
|
|
const workflow = parse(
|
|
readFileSync(join(projectDir, '.github/workflows/computer-e2e.yml'), 'utf8')
|
|
)
|
|
|
|
expect(workflow.concurrency).toEqual({
|
|
group: 'computer-e2e-${{ github.event.pull_request.number || github.ref }}',
|
|
'cancel-in-progress': "${{ github.event_name == 'pull_request' }}"
|
|
})
|
|
})
|
|
|
|
it('runs computer-use e2e files serially because they share desktop focus', () => {
|
|
const config = readFileSync(join(projectDir, 'tests/e2e/vitest.config.ts'), 'utf8')
|
|
|
|
expect(config).toContain('fileParallelism: false')
|
|
})
|
|
|
|
it('guards e2e source against fragile waits and Windows Calculator drift', () => {
|
|
const driver = readFileSync(join(projectDir, 'tests/e2e/helpers/computer-driver.ts'), 'utf8')
|
|
const cliDriver = readFileSync(
|
|
join(projectDir, 'tests/e2e/helpers/computer-cli-driver.ts'),
|
|
'utf8'
|
|
)
|
|
const windowsStoreE2e = readFileSync(
|
|
join(projectDir, 'tests/e2e/computer-windows-store.e2e.ts'),
|
|
'utf8'
|
|
)
|
|
|
|
expect(driver).not.toContain('await delay(3500)')
|
|
expect(driver).toContain("await waitForComputerWindowTitle('gedit', fileName, 15000)")
|
|
expect(cliDriver).toContain('ORCA_DEV_USER_DATA_PATH')
|
|
expect(cliDriver).toContain('orca-computer-runtime-')
|
|
expect(cliDriver).toContain('retryMissingRuntimeMetadata')
|
|
expect(cliDriver).toContain('Could not read Orca runtime metadata')
|
|
expect(cliDriver).toContain("'serve', '--no-pairing', '--json'")
|
|
|
|
expect(windowsStoreE2e).toContain("app.bundleId === 'ApplicationFrameHost'")
|
|
expect(windowsStoreE2e).toContain("app.bundleId === 'win32calc'")
|
|
expect(windowsStoreE2e).toContain('buttonIndex >= 0')
|
|
expect(windowsStoreE2e).toContain('pane(?:\\s|$)/m')
|
|
expect(windowsStoreE2e).toContain('String(clickIndex)')
|
|
expect(windowsStoreE2e).not.toContain(
|
|
"for (const buttonName of ['One', 'Plus', 'Two', 'Equals'])"
|
|
)
|
|
})
|
|
|
|
it('triggers on computer-use shared contracts, scripts, and agent skill changes', () => {
|
|
const workflow = parse(
|
|
readFileSync(join(projectDir, '.github/workflows/computer-e2e.yml'), 'utf8')
|
|
)
|
|
const triggerPaths = workflow.on.pull_request.paths
|
|
|
|
expect(triggerPaths).toEqual(
|
|
expect.arrayContaining([
|
|
'config/scripts/computer-e2e-workflow.test.mjs',
|
|
'config/scripts/macos-computer-helper-owner-loss-group-recovery.test.mjs',
|
|
'config/scripts/computer-use-modifier-safety.test.mjs',
|
|
'config/scripts/computer-use-skill-guidance.test.mjs',
|
|
'config/scripts/computer-use-smoke.mjs',
|
|
'config/scripts/computer-use-smoke.test.mjs',
|
|
'skills/computer-use/SKILL.md',
|
|
'src/main/computer/**',
|
|
'src/main/runtime/rpc/dispatcher.ts',
|
|
'src/main/runtime/rpc/errors.ts',
|
|
'src/main/runtime/rpc/methods/computer*.ts',
|
|
'src/shared/computer-use-*.ts',
|
|
'tests/e2e/vitest.config.ts'
|
|
])
|
|
)
|
|
expect(triggerPaths).not.toContain('src/shared/runtime-types.ts')
|
|
})
|
|
|
|
it('runs focused computer-use regression tests in the PR native-smoke job', () => {
|
|
const workflow = parse(
|
|
readFileSync(join(projectDir, '.github/workflows/computer-e2e.yml'), 'utf8')
|
|
)
|
|
const nativeSmokeRuns = workflow.jobs['native-smoke'].steps
|
|
.map((step) => step.run)
|
|
.filter((run) => typeof run === 'string')
|
|
const checkout = workflow.jobs['native-smoke'].steps.find(
|
|
(step) => step.uses === 'actions/checkout@v6'
|
|
)
|
|
const regressionRun = nativeSmokeRuns.find((run) => run.includes('pnpm vitest run'))
|
|
const expectedRegressionFiles = [
|
|
'config/scripts/macos-computer-helper-owner-loss-group-recovery.test.mjs',
|
|
'config/scripts/macos-computer-helper-owner-loss-processes.test.mjs',
|
|
'config/scripts/computer-use-modifier-safety.test.mjs',
|
|
'config/scripts/computer-use-skill-guidance.test.mjs',
|
|
'config/scripts/computer-use-smoke.test.mjs',
|
|
'src/main/computer/computer-provider-lifecycle.test.ts',
|
|
'src/main/computer/computer-provider-unavailable-message.test.ts',
|
|
'src/main/computer/sidecar-client.test.ts',
|
|
'src/main/computer/macos-native-provider-client.test.ts',
|
|
'src/main/computer/macos-native-provider-socket.test.ts',
|
|
'src/main/computer/macos-computer-use-permissions.test.ts',
|
|
'src/main/computer/macos-computer-use-permission-status.test.ts',
|
|
'src/main/computer/desktop-script-provider-client.test.ts',
|
|
'src/main/computer/desktop-script-provider-cache.test.ts',
|
|
'src/main/computer/desktop-script-provider-actions.test.ts',
|
|
'src/main/computer/desktop-script-provider-cache-lifecycle.test.ts',
|
|
'src/main/computer/desktop-script-provider-errors.test.ts',
|
|
'src/main/computer/desktop-script-provider-action-errors.test.ts',
|
|
'src/shared/computer-use-error-recovery.test.ts',
|
|
'src/shared/computer-use-key-spec.test.ts',
|
|
'src/cli/format.test.ts',
|
|
'src/cli/handlers/computer.test.ts',
|
|
'src/cli/handlers/computer-action-routing.test.ts',
|
|
'src/cli/handlers/computer-action-validation.test.ts',
|
|
'src/cli/handlers/computer-state-formatting.test.ts',
|
|
'src/cli/specs/computer.test.ts',
|
|
'src/cli/index.test.ts',
|
|
'src/main/runtime/rpc/dispatcher-computer-errors.test.ts',
|
|
'src/main/runtime/rpc/errors.test.ts',
|
|
'src/main/runtime/rpc/methods/computer.test.ts',
|
|
'src/main/runtime/rpc/methods/computer-actions.test.ts',
|
|
'src/cli/runtime/envelope-schema.test.ts',
|
|
'src/shared/remote-runtime-client.test.ts'
|
|
]
|
|
|
|
expect(checkout.with['persist-credentials']).toBe(false)
|
|
expect(regressionRun).toBeTruthy()
|
|
for (const file of expectedRegressionFiles) {
|
|
expect(regressionRun).toContain(file)
|
|
}
|
|
})
|
|
|
|
it('keeps Linux native imports available without installing the GUI-only stack in PR smoke', () => {
|
|
const workflow = parse(
|
|
readFileSync(join(projectDir, '.github/workflows/computer-e2e.yml'), 'utf8')
|
|
)
|
|
const nativeSmokeInstall = workflow.jobs['native-smoke'].steps.find(
|
|
(step) => step.if === "runner.os == 'Linux'"
|
|
)
|
|
const scheduledLinuxInstall = workflow.jobs.linux.steps.find((step) =>
|
|
step.run?.includes('apt-get install')
|
|
)
|
|
|
|
expect(nativeSmokeInstall.run).toContain('python3')
|
|
expect(nativeSmokeInstall.run).toContain('python3-gi')
|
|
expect(nativeSmokeInstall.run).toContain('gir1.2-atspi-2.0')
|
|
expect(nativeSmokeInstall.run).toContain('at-spi2-core')
|
|
expect(nativeSmokeInstall.run).not.toContain('gedit')
|
|
expect(nativeSmokeInstall.run).not.toContain('xvfb')
|
|
expect(nativeSmokeInstall.run).not.toContain('xdotool')
|
|
expect(scheduledLinuxInstall.run).toContain('gedit')
|
|
expect(scheduledLinuxInstall.run).toContain('xvfb')
|
|
expect(scheduledLinuxInstall.run).toContain('xdotool')
|
|
})
|
|
|
|
it('builds and tests the macOS helper on every trigger without hosted TCC e2e', () => {
|
|
const workflow = parse(
|
|
readFileSync(join(projectDir, '.github/workflows/computer-e2e.yml'), 'utf8')
|
|
)
|
|
const job = workflow.jobs['mac-native-owner-smoke']
|
|
const runs = job.steps.map((step) => step.run).filter((run) => typeof run === 'string')
|
|
const checkout = job.steps.find((step) => step.uses === 'actions/checkout@v6')
|
|
const install = job.steps.find(
|
|
(step) => step.uses === './.github/actions/install-node-dependencies'
|
|
)
|
|
|
|
expect(job.if).toBeUndefined()
|
|
expect(job['runs-on']).toBe('macos-15')
|
|
expect(checkout.with['persist-credentials']).toBe(false)
|
|
expect(install.with['native-runtime']).toBe('electron')
|
|
expect(runs).toContain('pnpm bench:macos-computer-helper-owner-loss --expect reaped --trials 1')
|
|
const cleanupRun = runs.find((run) =>
|
|
run.includes('config/scripts/macos-computer-helper-owner-loss-processes.test.mjs')
|
|
)
|
|
expect(cleanupRun).toContain(
|
|
'config/scripts/macos-computer-helper-owner-loss-group-recovery.test.mjs'
|
|
)
|
|
expect(runs).toContain('pnpm verify:computer-native')
|
|
expect(
|
|
runs.find((run) => run.includes('config/scripts/build-native-for-platform.test.mjs'))
|
|
).toContain('--config config/vitest.config.ts')
|
|
expect(runs.join('\n')).not.toContain('test:e2e:computer')
|
|
expect(workflow.jobs.mac).toBeUndefined()
|
|
expect(workflow.on.pull_request.paths).toEqual(
|
|
expect.arrayContaining([
|
|
'config/scripts/build-native-for-platform.mjs',
|
|
'config/scripts/build-native-for-platform.test.mjs',
|
|
'config/scripts/macos-computer-helper-owner-loss-benchmark.mjs',
|
|
'config/scripts/macos-computer-helper-owner-loss-group-recovery.test.mjs',
|
|
'config/scripts/macos-computer-helper-owner-loss-metrics.mjs',
|
|
'config/scripts/macos-computer-helper-owner-loss-processes.mjs',
|
|
'config/scripts/macos-computer-helper-owner-loss-processes.test.mjs',
|
|
'config/scripts/macos-computer-helper-owner-loss-trial-cleanup.mjs'
|
|
])
|
|
)
|
|
})
|
|
|
|
it('uses the cached Electron dependency path for scheduled Linux and Windows e2e', () => {
|
|
const workflow = parse(
|
|
readFileSync(join(projectDir, '.github/workflows/computer-e2e.yml'), 'utf8')
|
|
)
|
|
for (const jobName of ['linux', 'windows']) {
|
|
const job = workflow.jobs[jobName]
|
|
const checkout = job.steps.find((step) => step.uses === 'actions/checkout@v6')
|
|
const install = job.steps.find(
|
|
(step) => step.uses === './.github/actions/install-node-dependencies'
|
|
)
|
|
expect(checkout.with['persist-credentials'], jobName).toBe(false)
|
|
expect(install.with['native-runtime'], jobName).toBe('electron')
|
|
expect(
|
|
job.steps.some((step) => step.uses === 'pnpm/setup@v2'),
|
|
jobName
|
|
).toBe(false)
|
|
expect(
|
|
job.steps.some((step) => step.run === 'pnpm install --frozen-lockfile'),
|
|
jobName
|
|
).toBe(false)
|
|
}
|
|
})
|
|
|
|
it('runs deterministic macOS owner-loss benchmark cleanup coverage', () => {
|
|
const benchmark = readFileSync(
|
|
join(projectDir, 'config/scripts/macos-computer-helper-owner-loss-benchmark.mjs'),
|
|
'utf8'
|
|
)
|
|
const cleanup = readFileSync(
|
|
join(projectDir, 'config/scripts/macos-computer-helper-owner-loss-trial-cleanup.mjs'),
|
|
'utf8'
|
|
)
|
|
|
|
expect(benchmark).toContain('spawnBenchmarkProcess(executable, [launcherDir]')
|
|
expect(benchmark).toContain("stdio: ['ignore', stdoutDescriptor, stderrDescriptor]")
|
|
expect(benchmark).toContain('cleanupOwnerLossTrial({')
|
|
const parseIndex = benchmark.indexOf('parseBenchmarkTrialResult(serializedResult)')
|
|
const cleanupIndex = benchmark.indexOf('cleanupOwnerLossTrial({')
|
|
expect(parseIndex).toBeGreaterThanOrEqual(0)
|
|
expect(cleanupIndex).toBeGreaterThanOrEqual(0)
|
|
expect(parseIndex).toBeLessThan(cleanupIndex)
|
|
expect(benchmark).toContain('trialCleanupSha256: artifactSha256(trialCleanupPath)')
|
|
expect(cleanup).toContain('killRecordedAndMatchingProcesses(options.recordPath')
|
|
expect(cleanup).toContain("signalValidatedProcessGroup(options.pid, options.marker, 'SIGKILL'")
|
|
})
|
|
|
|
it('boots the built daemon under plain Node in the PR native-smoke job after the main build', () => {
|
|
const workflow = parse(
|
|
readFileSync(join(projectDir, '.github/workflows/computer-e2e.yml'), 'utf8')
|
|
)
|
|
const steps = workflow.jobs['native-smoke'].steps
|
|
const runs = steps.map((step) => step.run).filter((run) => typeof run === 'string')
|
|
const buildIndex = runs.indexOf('pnpm run build:electron-vite:parallel')
|
|
const daemonSmokeIndex = runs.indexOf('node config/scripts/daemon-boot-smoke.mjs')
|
|
|
|
expect(daemonSmokeIndex, 'native-smoke must boot the built daemon').toBeGreaterThanOrEqual(0)
|
|
expect(
|
|
buildIndex,
|
|
'daemon boot smoke must run after the main bundle is built'
|
|
).toBeGreaterThanOrEqual(0)
|
|
expect(daemonSmokeIndex).toBeGreaterThan(buildIndex)
|
|
})
|
|
|
|
it('runs the Windows workspace-close daemon repro after the main build', () => {
|
|
const workflow = parse(
|
|
readFileSync(join(projectDir, '.github/workflows/computer-e2e.yml'), 'utf8')
|
|
)
|
|
const steps = workflow.jobs['native-smoke'].steps
|
|
const buildIndex = steps.findIndex(
|
|
(step) => step.run === 'pnpm run build:electron-vite:parallel'
|
|
)
|
|
const reproIndex = steps.findIndex(
|
|
(step) => step.run === 'node config/scripts/windows-daemon-workspace-close-repro.mjs'
|
|
)
|
|
|
|
expect(reproIndex).toBeGreaterThan(buildIndex)
|
|
expect(steps[reproIndex].if).toBe("runner.os == 'Windows'")
|
|
expect(workflow.on.pull_request.paths).toContain(
|
|
'config/scripts/windows-daemon-workspace-close-repro.mjs'
|
|
)
|
|
})
|
|
|
|
it('re-runs the native-smoke job when the daemon bundle graph changes', () => {
|
|
const workflow = parse(
|
|
readFileSync(join(projectDir, '.github/workflows/computer-e2e.yml'), 'utf8')
|
|
)
|
|
const triggerPaths = workflow.on.pull_request.paths
|
|
|
|
expect(triggerPaths).toEqual(
|
|
expect.arrayContaining([
|
|
'config/scripts/daemon-boot-smoke.mjs',
|
|
'config/scripts/windows-daemon-workspace-close-repro.mjs',
|
|
'electron.vite.config.ts',
|
|
'config/build-plugins/**',
|
|
'src/main/daemon/**'
|
|
])
|
|
)
|
|
})
|
|
|
|
it('does not run computer-use e2e in PR smoke jobs', () => {
|
|
const workflow = parse(
|
|
readFileSync(join(projectDir, '.github/workflows/computer-e2e.yml'), 'utf8')
|
|
)
|
|
const nativeSmokeRuns = workflow.jobs['native-smoke'].steps
|
|
.map((step) => step.run)
|
|
.filter((run) => typeof run === 'string')
|
|
|
|
expect(nativeSmokeRuns.join('\n')).not.toContain('test:e2e:computer')
|
|
})
|
|
|
|
it('builds Electron main output before every computer-use e2e run', () => {
|
|
const workflow = parse(
|
|
readFileSync(join(projectDir, '.github/workflows/computer-e2e.yml'), 'utf8')
|
|
)
|
|
|
|
for (const jobName of ['native-smoke', 'linux', 'windows']) {
|
|
const runs = workflow.jobs[jobName].steps
|
|
.map((step) => step.run)
|
|
.filter((run) => typeof run === 'string')
|
|
const buildIndex = runs.indexOf('pnpm run build:electron-vite:parallel')
|
|
const e2eIndexes = runs
|
|
.map((run, index) => (run.includes('test:e2e:computer') ? index : -1))
|
|
.filter((index) => index >= 0)
|
|
|
|
expect(
|
|
buildIndex,
|
|
`${jobName} should build out/main before computer e2e`
|
|
).toBeGreaterThanOrEqual(0)
|
|
for (const e2eIndex of e2eIndexes) {
|
|
expect(buildIndex, `${jobName} should build out/main before computer e2e`).toBeLessThan(
|
|
e2eIndex
|
|
)
|
|
}
|
|
}
|
|
})
|
|
|
|
it('keeps computer-use e2e in scheduled jobs only', () => {
|
|
const workflow = parse(
|
|
readFileSync(join(projectDir, '.github/workflows/computer-e2e.yml'), 'utf8')
|
|
)
|
|
const nativeSmokeRuns = workflow.jobs['native-smoke'].steps
|
|
.map((step) => step.run)
|
|
.filter((run) => typeof run === 'string')
|
|
const allRuns = [
|
|
...nativeSmokeRuns,
|
|
...workflow.jobs.linux.steps.map((step) => step.run).filter((run) => typeof run === 'string'),
|
|
...workflow.jobs.windows.steps
|
|
.map((step) => step.run)
|
|
.filter((run) => typeof run === 'string')
|
|
]
|
|
|
|
expect(nativeSmokeRuns.join('\n')).not.toContain('test:e2e:computer')
|
|
expect(allRuns.join('\n')).toContain('test:e2e:computer')
|
|
expect(allRuns.join('\n')).not.toContain('test:e2e:computer -- --reporter')
|
|
})
|
|
|
|
it('runs Linux e2e on schedule without advertising hosted macOS TCC coverage', () => {
|
|
const workflow = parse(
|
|
readFileSync(join(projectDir, '.github/workflows/computer-e2e.yml'), 'utf8')
|
|
)
|
|
const triggerPaths = workflow.on.pull_request.paths
|
|
const linuxRuns = workflow.jobs.linux.steps
|
|
.map((step) => step.run)
|
|
.filter((run) => typeof run === 'string')
|
|
|
|
expect(triggerPaths).toEqual(
|
|
expect.arrayContaining([
|
|
'tests/e2e/computer-linux.e2e.ts',
|
|
'tests/e2e/helpers/computer-cli-driver.ts',
|
|
'tests/e2e/helpers/computer-driver.ts'
|
|
])
|
|
)
|
|
expect(triggerPaths).not.toContain('tests/e2e/computer-mac.e2e.ts')
|
|
expect(triggerPaths).not.toContain('tests/e2e/computer-mac-safari.e2e.ts')
|
|
expect(workflow.jobs.mac).toBeUndefined()
|
|
expect(linuxRuns).toContain(
|
|
'xvfb-run --auto-servernum dbus-run-session -- pnpm test:e2e:computer --reporter=verbose tests/e2e/computer-linux.e2e.ts'
|
|
)
|
|
})
|
|
|
|
it('runs every Windows computer-use e2e file in the scheduled Windows job', () => {
|
|
const workflow = parse(
|
|
readFileSync(join(projectDir, '.github/workflows/computer-e2e.yml'), 'utf8')
|
|
)
|
|
const triggerPaths = workflow.on.pull_request.paths
|
|
const windowsRuns = workflow.jobs.windows.steps
|
|
.map((step) => step.run)
|
|
.filter((run) => typeof run === 'string')
|
|
|
|
expect(triggerPaths).toEqual(
|
|
expect.arrayContaining([
|
|
'tests/e2e/computer-windows.e2e.ts',
|
|
'tests/e2e/computer-windows-store.e2e.ts'
|
|
])
|
|
)
|
|
expect(windowsRuns).toContain(
|
|
'pnpm test:e2e:computer --reporter=verbose tests/e2e/computer-windows.e2e.ts tests/e2e/computer-windows-store.e2e.ts'
|
|
)
|
|
})
|
|
})
|