Flaky Cypress and Playwright tests on GitHub Actions: when the runner is the cause
End-to-end tests that pass on your laptop and flake on GitHub Actions are often starved, not broken. On a private repository's ubuntu-latest, Cypress or Playwright, a browser, your app server and its database share 2 CPUs and 8 GB, so pages render late, a command runs past its timeout (4 seconds by default in Cypress), or the browser crashes mid-run. Cypress's own docs say resource starvation "presents as flaky, slow, or apparently random failures" and that their example app runs comfortably on 4 vCPUs and 16 GB, twice a private runner's. Confirm it by watching CPU and memory during a failing run, fix fixed waits and shared data in the tests, and give the job the machine it needs.
Skipbox gives it that machine at a flat price: 8 vCPU and 16 GB on runs-on: skipbox-8x16 for $79 a month, on your account's own machine where the browsers and packages stay installed between runs. Minutes are unlimited, so retries and shards don't add to the bill.
What the tests share the runner with
An E2E job runs more than the tests. From Cypress's performance guide: "Cypress itself, the browser, your application server, and any background services your app depends on". On a private repository's ubuntu-latest they share 2 CPUs and 8 GB (GitHub-hosted runners). A public repository's gets 4 CPUs and 16 GB, which is the size Cypress names as comfortable for its example app.
The signs of a starved machine, from the same guide:
- Tests get slower as the run goes on
- The browser crashes mid-run with no clear test failure
- CPU or memory sits near 100% during the run
Playwright starts one browser per worker, and its default is half the cores. Its CI docs recommend workers: 1 in CI "to prioritize stability and reproducibility", so each test gets the whole machine, and suggest parallel tests only on "a powerful self-hosted CI system".
Confirm it before you change anything
- Cypress: run with
DEBUG=cypress:server:util:process_profiler npx cypress run. It prints CPU and memory every 10 seconds; CPU consistently above 100% means the machine is saturated. - Playwright: rerun the failing suite with
--workers=1. If the flakes stop, the machine was the limit, not the tests. Keep the trace on retry (trace: 'on-first-retry') to see what the page was doing when it failed. - Either: add a step before the tests,
nproc && free -h, and one in the background that logstop -b -n 1 | head -15every 15 seconds.
A test that fails in the same place on a fast machine is a flaky test, and more hardware won't fix it.
Fix what belongs to the tests
- No fixed waits.
cy.wait(2000)andpage.waitForTimeout()pass on a fast laptop and fail on a slow runner. Wait on the element, the route or the response instead. - Register intercepts before the request. A
cy.interceptset after the click races the request. - Isolate data. Tests that share a user or a record fail when they run in another order or in parallel.
- Turn retries on in CI. Playwright marks a test that fails and then passes as flaky in its report; Cypress's
retriessetting runs a failed test again incypress run. Retries show you the flaky ones. They don't fix them.
What sharding costs
Sharding splits the suite across jobs, each on its own machine. Playwright takes --shard=1/4 and merges the blob reports in a final job; Cypress's parallel mode needs recording to Cypress Cloud, which balances specs across machines.
Each shard is a new GitHub VM: it installs packages, downloads the browser (Playwright advises against caching browsers, since a restore takes about as long as the download), builds and starts your app. Four shards pay that four times, and per-minute billing counts every one. Sharding helps when the machine was the limit, but it multiplies the setup with it.
What a slow, flaky suite costs
E2E tests are the last check before merge, so the whole team waits on them, and a flake adds a re-run on top. On per-minute billing every one of those minutes is billed:
| Minutes a month (4 vCPU) | GitHub | Skipbox S | You save each month |
|---|---|---|---|
| 10,000 | $102 | $39 | $63 |
| 20,000 | $222 | $39 | $183 |
| 30,000 | $342 | $39 | $303 |
At 8 vCPU, 20,000 minutes are $422 on GitHub and $79 on Skipbox M. GitHub's figures are after GitHub Team's included usage. Your own minutes go in the calculator on the home page.
Give the suite the machine
Each plan comes with your account's own machine, kept between jobs, at a flat price:
| Label | Machine | Plan |
|---|---|---|
skipbox | 4 vCPU · 16 GB | S, $39 a month |
skipbox-8x16 | 8 vCPU · 16 GB | M, $79 a month |
skipbox-16x32 | 16 vCPU · 32 GB | L, $149 a month |
- CPU and memory for the whole stack.
skipbox-8x16has the cores and memory Cypress names for its example app and more cores, so the browser, the app and its database stop competing. With 8 vCPU, Playwright's default is 4 workers. - Browsers and packages stay. Playwright's browsers in
~/.cache/ms-playwright, the Cypress binary in~/.cache/Cypress, npm's cache and the Docker images of your services are on the disk when the next job starts. - Unlimited minutes. Retries, re-runs and shards don't change the bill. Your plans' vCPU set how many shards run at once; shards past that wait their turn.
Moving is one line
jobs:
e2e:
runs-on: skipbox-8x16 # was ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: lts/*
- run: npm ci
- run: npx playwright install --with-deps
- run: npx playwright test
skipbox-8x16 runs on an M plan; the trial runs runs-on: skipbox (4 vCPU, 16 GB).
- Sign in to Skipbox with GitHub.
- Install the Skipbox GitHub App and pick the repositories.
- Change
runs-onand push.
Security
- The machine is your account's alone. No other company's jobs run on it.
- After every job, the runner's credentials, processes and containers are wiped. The browsers and caches stay. Clean every job gives each job a fresh machine instead.
- Code from people without write access (such as pull requests from forks) runs on a fresh VM that reads your caches but can't change them.
The details are in how jobs are isolated.
Try it on the flaky suite
The trial is free for 7 days or 3,000 job-minutes, whichever comes first, with no card. Move the E2E job to runs-on: skipbox, run it a few times, and count the flakes and the step timings against GitHub's.
FAQ
Why are my Cypress tests flaky on GitHub Actions but pass locally?
Often because the runner is smaller than your laptop. A private repository's ubuntu-latest has 2 CPUs and 8 GB, and Cypress, its browser, your app server and its database all run on it. When they compete, pages render late, a command passes its 4-second default timeout, or the browser crashes. Cypress's own performance guide says resource starvation presents as flaky, slow or apparently random failures, and that its example app runs comfortably on 4 vCPUs and 16 GB. Confirm it with DEBUG=cypress:server:util:process_profiler, fix fixed waits, then give the job more CPU and memory.
Could the runner's CPU or memory be why Playwright tests flake in CI?
Yes. Each Playwright worker drives its own browser, and the default is half the machine's cores. Playwright's CI docs recommend one worker in CI for stability, because each test then gets the whole machine. If tests pass with --workers=1 and flake with more, the machine is the limit: keep one worker on 2 CPUs, or move to a machine with more cores and memory and raise the workers there.
How do I tell a starved runner from a flaky test?
Watch the machine during a failing run. Cypress prints CPU and memory every 10 seconds with DEBUG=cypress:server:util:process_profiler; for Playwright, run free -h and top in a background step, or rerun with --workers=1. A starved runner shows CPU near 100% or memory near the limit, and failures that move between tests and get worse later in the run. A flaky test fails in the same place on a fast machine too.
Does sharding E2E tests fix flakiness or cost?
Neither by itself. Sharding gives each part of the suite its own machine, which helps when the machine was the limit, but each shard installs packages, downloads browsers and starts your app again, and per-minute billing counts every shard. Cypress's parallel mode also needs recording to Cypress Cloud. On Skipbox the browsers and packages stay on your machine and minutes are unlimited, so shards and retries cost nothing extra.
What runner size should E2E tests use?
Start from what the tests run beside: the browser, the app server and its services. Cypress's example app runs comfortably on 4 vCPUs and 16 GB. On Skipbox, runs-on: skipbox-8x16 gives 8 vCPU and 16 GB for $79 a month, and skipbox-16x32 16 vCPU and 32 GB.
Can I try it for free?
Yes. The trial is 7 days or 3,000 job-minutes, whichever comes first, with no card, on 4 vCPU and 16 GB. Move the E2E job to runs-on: skipbox, run it a few times, and compare the failures and step timings with GitHub's.
Updated November 13, 2026