Skip to content

Commit 0a27ad9

Browse files
DavertMikclaude
andcommitted
test(bench): obscura vs playwright benchmark suite
examples/bench/ runs the same 10-scenario suite (navigations, click by css and by link text, two form fill/submit round trips, a non-navigating click repeated, repeated navigation, a short explicit wait) against Obscura and Playwright, through two minimal, default-settings configs (no plugins, default waitForAction/waitForTimeout) against the local PHP test app on :8000 -- the only fair arena, since no helper can be asked to beat a real network twice over. run-bench.mjs spawns `codecept run` N=3 times per engine (helper/browser startup included in the timed wall-clock, since that's part of real-world speed and is where Obscura's self-launch legitimately wins), reports each engine's median, and the ratio between them. Baseline (before this round's CDPBrowser perf work): Playwright median 4479ms, Obscura median 1787ms, ratio 2.51x. After (this commit's sibling, perf(CDPBrowser): event-aware action settle, plus the Obscura _waitForServer poll interval fix): Obscura median ~1020-1050ms, ratio ~4.28-4.36x across repeated bench runs -- comfortably over the 2x gate. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 1c976b1 commit 0a27ad9

4 files changed

Lines changed: 186 additions & 0 deletions

File tree

examples/bench/bench_test.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
Feature('Bench');
2+
3+
Scenario('01 navigate home and read text', ({ I }) => {
4+
I.amOnPage('/');
5+
I.see('Welcome to test app');
6+
I.dontSee('NoSuchTextXYZ123');
7+
});
8+
9+
Scenario('02 click by link text navigates', ({ I }) => {
10+
I.amOnPage('/');
11+
I.click('More info');
12+
I.seeInCurrentUrl('/info');
13+
I.see('Information');
14+
});
15+
16+
Scenario('03 click by css navigates', ({ I }) => {
17+
I.amOnPage('/');
18+
I.click('#link');
19+
I.seeInCurrentUrl('/info');
20+
});
21+
22+
Scenario('04 fill and submit login form', ({ I }) => {
23+
I.amOnPage('/login');
24+
I.fillField('#email', 'user@example.com');
25+
I.fillField('#password', 'secret123');
26+
I.click('Sign In');
27+
I.seeInCurrentUrl('/login');
28+
});
29+
30+
Scenario('05 fill a two-field form and submit', ({ I }) => {
31+
I.amOnPage('/form/example1');
32+
I.fillField('#LoginForm_username', 'demo');
33+
I.fillField('#LoginForm_password', 'demo');
34+
I.click('Login');
35+
I.see('I am here!!!');
36+
});
37+
38+
Scenario('06 click a non-navigating checkbox', ({ I }) => {
39+
I.amOnPage('/form/checkbox');
40+
I.click('#checkin');
41+
I.see('ticked');
42+
});
43+
44+
Scenario('07 submit a button form', ({ I }) => {
45+
I.amOnPage('/form/button');
46+
I.click('Submit');
47+
I.see('Thank you!');
48+
});
49+
50+
Scenario('08 repeated navigation', ({ I }) => {
51+
I.amOnPage('/');
52+
I.amOnPage('/info');
53+
I.amOnPage('/');
54+
I.see('Welcome to test app');
55+
});
56+
57+
Scenario('09 click a non-navigating element twice', ({ I }) => {
58+
I.amOnPage('/form/checkbox');
59+
I.click('#checkin');
60+
I.click('#checkin');
61+
I.see('ticked');
62+
});
63+
64+
Scenario('10 short explicit wait', ({ I }) => {
65+
I.amOnPage('/');
66+
I.wait(0.3);
67+
I.see('Welcome to test app');
68+
});
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export const config = {
2+
tests: './bench_test.js',
3+
output: './output',
4+
timeout: 30,
5+
helpers: {
6+
Obscura: {
7+
url: process.env.SITE_URL || 'http://127.0.0.1:8000',
8+
},
9+
},
10+
mocha: {},
11+
name: 'bench-obscura',
12+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
export const config = {
2+
tests: './bench_test.js',
3+
output: './output',
4+
timeout: 30,
5+
helpers: {
6+
Playwright: {
7+
url: process.env.SITE_URL || 'http://127.0.0.1:8000',
8+
show: false,
9+
restart: false,
10+
browser: 'chromium',
11+
chromium: {
12+
args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage'],
13+
},
14+
},
15+
},
16+
mocha: {},
17+
name: 'bench-playwright',
18+
}

examples/bench/run-bench.mjs

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
#!/usr/bin/env node
2+
// Runs the bench_test.js scenario suite N times against each engine's config, timing the whole
3+
// `codecept run` process wall-clock (helper/browser startup included, since that is part of
4+
// real-world speed) and reporting the median per engine plus the ratio between them.
5+
import { spawnSync } from 'child_process'
6+
import { fileURLToPath } from 'url'
7+
import { dirname, join } from 'path'
8+
9+
const __dirname = dirname(fileURLToPath(import.meta.url))
10+
const repoRoot = join(__dirname, '..', '..')
11+
const codeceptBin = join(repoRoot, 'bin', 'codecept.js')
12+
13+
const RUNS = Number(process.env.BENCH_RUNS || 3)
14+
const SITE_URL = process.env.SITE_URL || 'http://127.0.0.1:8000'
15+
16+
const engines = [
17+
{ name: 'Obscura', config: 'codecept.obscura.bench.js' },
18+
{ name: 'Playwright', config: 'codecept.playwright.bench.js' },
19+
]
20+
21+
function median(nums) {
22+
const sorted = [...nums].sort((a, b) => a - b)
23+
const mid = Math.floor(sorted.length / 2)
24+
return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2
25+
}
26+
27+
async function siteIsUp() {
28+
try {
29+
const res = await fetch(SITE_URL + '/')
30+
return res.status < 500
31+
} catch (e) {
32+
return false
33+
}
34+
}
35+
36+
function runOnce(config) {
37+
const start = Date.now()
38+
const result = spawnSync(codeceptBin, ['run', '-c', config], {
39+
cwd: __dirname,
40+
env: { ...process.env, SITE_URL },
41+
encoding: 'utf8',
42+
})
43+
const elapsed = Date.now() - start
44+
return { elapsed, exitCode: result.status, stdout: result.stdout, stderr: result.stderr }
45+
}
46+
47+
async function main() {
48+
if (!(await siteIsUp())) {
49+
console.error(`Test app is not reachable at ${SITE_URL}. Start it first:\n php -S 127.0.0.1:8000 -t test/data/app`)
50+
process.exit(1)
51+
}
52+
53+
const results = {}
54+
for (const engine of engines) {
55+
console.log(`\n=== ${engine.name} (${RUNS} runs) ===`)
56+
const times = []
57+
for (let i = 1; i <= RUNS; i++) {
58+
const { elapsed, exitCode, stdout, stderr } = runOnce(engine.config)
59+
if (exitCode !== 0) {
60+
console.error(` run ${i}: FAILED (exit ${exitCode}) in ${elapsed}ms — excluded from median`)
61+
console.error(stdout?.slice(-2000))
62+
console.error(stderr?.slice(-2000))
63+
continue
64+
}
65+
console.log(` run ${i}: ${elapsed}ms`)
66+
times.push(elapsed)
67+
}
68+
if (!times.length) {
69+
console.error(` no successful runs for ${engine.name}`)
70+
process.exit(1)
71+
}
72+
results[engine.name] = { times, median: median(times) }
73+
}
74+
75+
console.log('\n=== Summary ===')
76+
for (const engine of engines) {
77+
const r = results[engine.name]
78+
console.log(`${engine.name}: median ${r.median}ms (runs: ${r.times.join(', ')}ms)`)
79+
}
80+
81+
const playwrightMedian = results.Playwright.median
82+
const obscuraMedian = results.Obscura.median
83+
const ratio = playwrightMedian / obscuraMedian
84+
console.log(`\nRatio (Playwright median / Obscura median): ${ratio.toFixed(2)}x`)
85+
console.log(ratio >= 2 ? 'GATE MET: Obscura is at least 2x faster than Playwright.' : 'GATE NOT MET: Obscura is less than 2x faster than Playwright.')
86+
}
87+
88+
main()

0 commit comments

Comments
 (0)