Skip to content

Commit d2c5855

Browse files
committed
feat: run every library against the shared scenarios
1 parent e1c41b9 commit d2c5855

13 files changed

Lines changed: 661 additions & 163 deletions

File tree

package-lock.json

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/css-selector-generator-benchmark/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
"@medv/finder": "^4.0.2",
1515
"css-selector-generator": "^3.9.4",
1616
"react": "^19.0.0",
17-
"react-dom": "^19.0.0"
17+
"react-dom": "^19.0.0",
18+
"css-selector-generator-scenarios": "*"
1819
},
1920
"devDependencies": {
2021
"@types/react": "^19.0.0",

packages/css-selector-generator-benchmark/src/App.css

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,3 +500,26 @@ body {
500500
color: #333;
501501
text-decoration: none;
502502
}
503+
504+
.scenario-conformance {
505+
margin-top: 2rem;
506+
}
507+
508+
.scenario-conformance .note {
509+
font-size: 0.8rem;
510+
font-weight: 400;
511+
opacity: 0.8;
512+
}
513+
514+
.scenario-conformance code {
515+
font-size: 0.8rem;
516+
word-break: break-all;
517+
}
518+
519+
.conformance-toggle {
520+
display: inline-flex;
521+
gap: 0.4rem;
522+
align-items: center;
523+
margin: 1rem 0;
524+
font-size: 0.9rem;
525+
}

packages/css-selector-generator-benchmark/src/App.tsx

Lines changed: 63 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,22 @@
1-
import { useState, useEffect, useLayoutEffect, useRef } from 'react';
2-
import { BenchmarkTable } from './components/BenchmarkTable';
3-
import { CapabilitiesComparison } from './components/CapabilitiesComparison';
4-
import { SelectorComparison } from './components/SelectorComparison';
5-
import { runBenchmark, type BenchmarkResults, type BenchmarkProgress } from './utils/benchmark';
6-
import simpleHtml from './data/simple.html?raw';
7-
import complexHtml from './data/complex.html?raw';
8-
9-
type HtmlPreset = 'simple' | 'complex';
1+
import { useState, useEffect, useLayoutEffect, useRef } from "react";
2+
import { BenchmarkTable } from "./components/BenchmarkTable";
3+
import { CapabilitiesComparison } from "./components/CapabilitiesComparison";
4+
import { SelectorComparison } from "./components/SelectorComparison";
5+
import { ScenarioConformance } from "./components/ScenarioConformance";
6+
import {
7+
runBenchmark,
8+
type BenchmarkResults,
9+
type BenchmarkProgress,
10+
} from "./utils/benchmark";
11+
import {
12+
runScenarioConformance,
13+
type ConformanceProgress,
14+
type LibraryConformance,
15+
} from "./utils/scenarioConformance";
16+
import simpleHtml from "./data/simple.html?raw";
17+
import complexHtml from "./data/complex.html?raw";
18+
19+
type HtmlPreset = "simple" | "complex";
1020

1121
const HTML_PRESETS: Record<HtmlPreset, string> = {
1222
simple: simpleHtml,
@@ -17,6 +27,11 @@ function App() {
1727
const [results, setResults] = useState<BenchmarkResults | null>(null);
1828
const [isRunning, setIsRunning] = useState(false);
1929
const [progress, setProgress] = useState<BenchmarkProgress | null>(null);
30+
const [conformance, setConformance] = useState<LibraryConformance[] | null>(
31+
null,
32+
);
33+
const [conformanceProgress, setConformanceProgress] =
34+
useState<ConformanceProgress | null>(null);
2035
const htmlContentRef = useRef(simpleHtml);
2136
const textareaRef = useRef<HTMLTextAreaElement>(null);
2237
const [isHtmlEditorExpanded, setIsHtmlEditorExpanded] = useState(false);
@@ -25,9 +40,11 @@ function App() {
2540
const el = textareaRef.current;
2641
if (!el) return;
2742
el.value = htmlContentRef.current;
28-
const handleInput = () => { htmlContentRef.current = el.value; };
29-
el.addEventListener('input', handleInput);
30-
return () => el.removeEventListener('input', handleInput);
43+
const handleInput = () => {
44+
htmlContentRef.current = el.value;
45+
};
46+
el.addEventListener("input", handleInput);
47+
return () => el.removeEventListener("input", handleInput);
3148
}, [isHtmlEditorExpanded]);
3249

3350
const handlePresetChange = (preset: HtmlPreset) => {
@@ -41,18 +58,25 @@ function App() {
4158
setIsRunning(true);
4259
setResults(null);
4360
setProgress(null);
61+
setConformance(null);
4462

4563
// Use setTimeout to allow UI to update before heavy computation
4664
setTimeout(async () => {
4765
const benchmarkResults = await runBenchmark(
4866
textareaRef.current?.value ?? htmlContentRef.current,
4967
(prog) => {
5068
setProgress(prog);
51-
}
69+
},
5270
);
5371
setResults(benchmarkResults);
54-
setIsRunning(false);
5572
setProgress(null);
73+
74+
const conformanceResults = await runScenarioConformance((prog) => {
75+
setConformanceProgress(prog);
76+
});
77+
setConformance(conformanceResults);
78+
setConformanceProgress(null);
79+
setIsRunning(false);
5680
}, 100);
5781
};
5882

@@ -67,6 +91,7 @@ function App() {
6791
<h1>CSS Selector Generator Benchmark</h1>
6892
<nav className="header-nav">
6993
<a href="#benchmark-results">Benchmark Results</a>
94+
<a href="#scenario-conformance">Scenario Conformance</a>
7095
<a href="#library-capabilities">Library Capabilities Comparison</a>
7196
</nav>
7297
</header>
@@ -77,21 +102,21 @@ function App() {
77102
className="html-editor-toggle"
78103
onClick={() => setIsHtmlEditorExpanded(!isHtmlEditorExpanded)}
79104
>
80-
{isHtmlEditorExpanded ? '▼' : '▶'} Custom HTML
105+
{isHtmlEditorExpanded ? "▼" : "▶"} Custom HTML
81106
</button>
82107
{isHtmlEditorExpanded && (
83108
<>
84109
<div className="html-presets">
85110
<span>Load preset:</span>
86111
<button
87112
className="preset-button"
88-
onClick={() => handlePresetChange('simple')}
113+
onClick={() => handlePresetChange("simple")}
89114
>
90115
Simple
91116
</button>
92117
<button
93118
className="preset-button"
94-
onClick={() => handlePresetChange('complex')}
119+
onClick={() => handlePresetChange("complex")}
95120
>
96121
Complex
97122
</button>
@@ -110,14 +135,26 @@ function App() {
110135
</div>
111136

112137
<div className="controls">
113-
<button className="button" onClick={handleRunBenchmark} disabled={isRunning}>
114-
{isRunning ? 'Running benchmark...' : 'Re-run Benchmark'}
138+
<button
139+
className="button"
140+
onClick={handleRunBenchmark}
141+
disabled={isRunning}
142+
>
143+
{isRunning ? "Running benchmark..." : "Re-run Benchmark"}
115144
</button>
116145
</div>
117146

118147
{isRunning && progress && (
119148
<div className="loading">
120-
Testing {progress.libraryName}: {progress.currentElement}/{progress.totalElements} elements
149+
Testing {progress.libraryName}: {progress.currentElement}/
150+
{progress.totalElements} elements
151+
</div>
152+
)}
153+
154+
{isRunning && conformanceProgress && (
155+
<div className="loading">
156+
Checking scenario {conformanceProgress.current}/
157+
{conformanceProgress.total}: {conformanceProgress.scenarioId}
121158
</div>
122159
)}
123160

@@ -129,6 +166,12 @@ function App() {
129166
</div>
130167
)}
131168

169+
{conformance && (
170+
<div id="scenario-conformance">
171+
<ScenarioConformance results={conformance} />
172+
</div>
173+
)}
174+
132175
<div id="library-capabilities">
133176
<CapabilitiesComparison />
134177
</div>

packages/css-selector-generator-benchmark/src/components/BenchmarkTable.tsx

Lines changed: 32 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
1-
import type { LibraryStats } from '../utils/benchmark';
1+
import type { LibraryStats } from "../utils/benchmark";
22

33
interface BenchmarkTableProps {
44
libraries: LibraryStats[];
55
}
66

77
const LIBRARY_URLS: Record<string, string> = {
8-
'css-selector-generator': 'https://github.com/fczbkk/css-selector-generator',
9-
'css-selector-generator (custom options)': 'https://github.com/fczbkk/css-selector-generator',
10-
'@medv/finder': 'https://github.com/antonmedv/finder',
11-
'@cypress/unique-selector': 'https://github.com/cypress-io/unique-selector',
8+
"css-selector-generator": "https://github.com/fczbkk/css-selector-generator",
9+
"css-selector-generator (custom options)":
10+
"https://github.com/fczbkk/css-selector-generator",
11+
"@medv/finder": "https://github.com/antonmedv/finder",
12+
"@cypress/unique-selector": "https://github.com/cypress-io/unique-selector",
1213
};
1314

1415
export function BenchmarkTable({ libraries }: BenchmarkTableProps) {
@@ -17,55 +18,55 @@ export function BenchmarkTable({ libraries }: BenchmarkTableProps) {
1718
const uniqueRate = (stats.uniqueCount / stats.totalCount) * 100;
1819

1920
switch (metric) {
20-
case 'totalElements':
21+
case "totalElements":
2122
return stats.totalCount;
22-
case 'selectorsGenerated':
23+
case "selectorsGenerated":
2324
return `${stats.successCount} (${successRate.toFixed(1)}%)`;
24-
case 'uniqueSelectors':
25+
case "uniqueSelectors":
2526
return `${stats.uniqueCount} (${uniqueRate.toFixed(1)}%)`;
26-
case 'totalTime':
27+
case "totalTime":
2728
return `${stats.totalTime.toFixed(2)}ms`;
28-
case 'averageTime':
29+
case "averageTime":
2930
return `${stats.averageTime.toFixed(3)}ms`;
30-
case 'fastestTime':
31+
case "fastestTime":
3132
return `${stats.fastestTime.toFixed(3)}ms`;
32-
case 'slowestTime':
33+
case "slowestTime":
3334
return `${stats.slowestTime.toFixed(3)}ms`;
34-
case 'avgSelectorLength':
35+
case "avgSelectorLength":
3536
return stats.averageSelectorLength.toFixed(1);
36-
case 'shortestSelector':
37+
case "shortestSelector":
3738
return stats.shortestSelector;
38-
case 'longestSelector':
39+
case "longestSelector":
3940
return stats.longestSelector;
4041
default:
41-
return '';
42+
return "";
4243
}
4344
};
4445

4546
const getCellClass = (stats: LibraryStats, metric: string): string => {
4647
const successRate = (stats.successCount / stats.totalCount) * 100;
4748
const uniqueRate = (stats.uniqueCount / stats.totalCount) * 100;
4849

49-
if (metric === 'selectorsGenerated') {
50-
return successRate === 100 ? 'success' : 'warning';
50+
if (metric === "selectorsGenerated") {
51+
return successRate === 100 ? "success" : "warning";
5152
}
52-
if (metric === 'uniqueSelectors') {
53-
return uniqueRate === 100 ? 'success' : 'error';
53+
if (metric === "uniqueSelectors") {
54+
return uniqueRate === 100 ? "success" : "error";
5455
}
55-
return '';
56+
return "";
5657
};
5758

5859
const metrics = [
59-
{ key: 'totalElements', label: 'Total Elements' },
60-
{ key: 'selectorsGenerated', label: 'Selectors Generated' },
61-
{ key: 'uniqueSelectors', label: 'Unique Selectors' },
62-
{ key: 'totalTime', label: 'Total Time' },
63-
{ key: 'averageTime', label: 'Average Time' },
64-
{ key: 'fastestTime', label: 'Fastest Time' },
65-
{ key: 'slowestTime', label: 'Slowest Time' },
66-
{ key: 'avgSelectorLength', label: 'Avg Selector Length' },
67-
{ key: 'shortestSelector', label: 'Shortest Selector' },
68-
{ key: 'longestSelector', label: 'Longest Selector' },
60+
{ key: "totalElements", label: "Total Elements" },
61+
{ key: "selectorsGenerated", label: "Selectors Generated" },
62+
{ key: "uniqueSelectors", label: "Unique Selectors" },
63+
{ key: "totalTime", label: "Total Time" },
64+
{ key: "averageTime", label: "Average Time" },
65+
{ key: "fastestTime", label: "Fastest Time" },
66+
{ key: "slowestTime", label: "Slowest Time" },
67+
{ key: "avgSelectorLength", label: "Avg Selector Length" },
68+
{ key: "shortestSelector", label: "Shortest Selector" },
69+
{ key: "longestSelector", label: "Longest Selector" },
6970
];
7071

7172
return (

0 commit comments

Comments
 (0)