@workglow/test-contract 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +232 -0
- package/dist/ai-provider/assertions/capabilityHonesty.d.ts +7 -0
- package/dist/ai-provider/assertions/dispose.d.ts +7 -0
- package/dist/ai-provider/assertions/inferAdvertisesRegistered.d.ts +15 -0
- package/dist/ai-provider/assertions/inferServesInferred.d.ts +20 -0
- package/dist/ai-provider/assertions/pricingMatchesModality.d.ts +36 -0
- package/dist/ai-provider/assertions/registryCoverage.d.ts +13 -0
- package/dist/ai-provider/assertions/sessionReuse.d.ts +7 -0
- package/dist/ai-provider/assertions/signalHonoring.d.ts +7 -0
- package/dist/ai-provider/assertions/structuredGeneration.d.ts +7 -0
- package/dist/ai-provider/assertions/textGenerationSmoke.d.ts +7 -0
- package/dist/ai-provider/assertions/toolCallAccumulator.d.ts +7 -0
- package/dist/ai-provider/assertions/toolCallMultiTurn.d.ts +7 -0
- package/dist/ai-provider/assertions/usageNormalization.d.ts +61 -0
- package/dist/ai-provider/fixtures.d.ts +8 -0
- package/dist/ai-provider/runAiProviderConformance.d.ts +7 -0
- package/dist/ai-provider/types.d.ts +75 -0
- package/dist/ai-provider.d.ts +25 -0
- package/dist/ai-provider.js +876 -0
- package/dist/ai-provider.js.map +26 -0
- package/dist/browser-context/ConformanceMockContext.d.ts +54 -0
- package/dist/browser-context/assertions/ariaRoundTrip.d.ts +7 -0
- package/dist/browser-context/assertions/capabilityHonesty.d.ts +7 -0
- package/dist/browser-context/assertions/itExpectFail.d.ts +8 -0
- package/dist/browser-context/assertions/networkIntrospection.d.ts +7 -0
- package/dist/browser-context/assertions/tabsLifecycle.d.ts +7 -0
- package/dist/browser-context/fixtures.d.ts +8 -0
- package/dist/browser-context/runIBrowserContextConformance.d.ts +7 -0
- package/dist/browser-context/types.d.ts +48 -0
- package/dist/browser-context.d.ts +18 -0
- package/dist/browser-context.js +567 -0
- package/dist/browser-context.js.map +17 -0
- package/dist/creditExhaustedSkip.d.ts +39 -0
- package/dist/creditExhaustedSkip.js +163 -0
- package/dist/creditExhaustedSkip.js.map +10 -0
- package/dist/entitlement-profile/assertions/denialShape.d.ts +13 -0
- package/dist/entitlement-profile/assertions/dispose.d.ts +16 -0
- package/dist/entitlement-profile/assertions/hierarchyHonoring.d.ts +11 -0
- package/dist/entitlement-profile/assertions/optionalNeverDenied.d.ts +11 -0
- package/dist/entitlement-profile/assertions/requestEntitlementShape.d.ts +12 -0
- package/dist/entitlement-profile/assertions/resourceScoping.d.ts +16 -0
- package/dist/entitlement-profile/assertions/subscribeGrant.d.ts +11 -0
- package/dist/entitlement-profile/assertions/subscribeReload.d.ts +11 -0
- package/dist/entitlement-profile/assertions/subscribeRevocation.d.ts +12 -0
- package/dist/entitlement-profile/assertions/surfaceCoverage.d.ts +7 -0
- package/dist/entitlement-profile/assertions/unsubscribeIdempotent.d.ts +7 -0
- package/dist/entitlement-profile/fixtures.d.ts +21 -0
- package/dist/entitlement-profile/runEntitlementProfileConformance.d.ts +7 -0
- package/dist/entitlement-profile/types.d.ts +45 -0
- package/dist/entitlement-profile.d.ts +23 -0
- package/dist/entitlement-profile.js +436 -0
- package/dist/entitlement-profile.js.map +22 -0
- package/dist/human-connector/MockHumanConnector.d.ts +29 -0
- package/dist/human-connector/assertions/abort.d.ts +7 -0
- package/dist/human-connector/assertions/capabilityHonesty.d.ts +7 -0
- package/dist/human-connector/assertions/concurrentIsolation.d.ts +7 -0
- package/dist/human-connector/assertions/multiTurnFollowUp.d.ts +7 -0
- package/dist/human-connector/assertions/notifyDisplayFastResolve.d.ts +7 -0
- package/dist/human-connector/assertions/roundtrip.d.ts +7 -0
- package/dist/human-connector/fixtures.d.ts +8 -0
- package/dist/human-connector/runHumanConnectorConformance.d.ts +9 -0
- package/dist/human-connector/types.d.ts +91 -0
- package/dist/human-connector.d.ts +19 -0
- package/dist/human-connector.js +843 -0
- package/dist/human-connector.js.map +20 -0
- package/dist/itExpectFail.d.ts +15 -0
- package/dist/itExpectFail.js +180 -0
- package/dist/itExpectFail.js.map +11 -0
- package/dist/storage-migrations/assertions/appliesAndRecords.d.ts +7 -0
- package/dist/storage-migrations/assertions/concurrentRunsSerialize.d.ts +18 -0
- package/dist/storage-migrations/assertions/ensureBookkeepingIdempotent.d.ts +7 -0
- package/dist/storage-migrations/assertions/failedMigrationLeavesNoPartialSchema.d.ts +14 -0
- package/dist/storage-migrations/assertions/failedMigrationNotRecorded.d.ts +7 -0
- package/dist/storage-migrations/assertions/idempotentRun.d.ts +7 -0
- package/dist/storage-migrations/assertions/incrementalApplication.d.ts +7 -0
- package/dist/storage-migrations/runMigrationRunnerContract.d.ts +9 -0
- package/dist/storage-migrations/types.d.ts +78 -0
- package/dist/storage-migrations.d.ts +18 -0
- package/dist/storage-migrations.js +473 -0
- package/dist/storage-migrations.js.map +20 -0
- package/dist/tabular-migrations/assertions/addAndDropIndex.d.ts +7 -0
- package/dist/tabular-migrations/assertions/addColumn.d.ts +7 -0
- package/dist/tabular-migrations/assertions/backfill.d.ts +7 -0
- package/dist/tabular-migrations/assertions/dropColumn.d.ts +7 -0
- package/dist/tabular-migrations/assertions/failedMigrationNotRecorded.d.ts +7 -0
- package/dist/tabular-migrations/assertions/freshDbFastPath.d.ts +7 -0
- package/dist/tabular-migrations/assertions/incrementalApplication.d.ts +7 -0
- package/dist/tabular-migrations/assertions/renameColumn.d.ts +7 -0
- package/dist/tabular-migrations/runTabularMigrationContract.d.ts +8 -0
- package/dist/tabular-migrations/types.d.ts +28 -0
- package/dist/tabular-migrations.d.ts +19 -0
- package/dist/tabular-migrations.js +257 -0
- package/dist/tabular-migrations.js.map +18 -0
- package/dist/tabular-storage/assertions/countMatchesQuery.d.ts +7 -0
- package/dist/tabular-storage/assertions/guardParity.d.ts +25 -0
- package/dist/tabular-storage/assertions/inListCriterion.d.ts +14 -0
- package/dist/tabular-storage/assertions/joinBoundedLeftRead.d.ts +29 -0
- package/dist/tabular-storage/assertions/joinPushdown.d.ts +13 -0
- package/dist/tabular-storage/assertions/notInListCriterion.d.ts +15 -0
- package/dist/tabular-storage/assertions/strategyParity.d.ts +31 -0
- package/dist/tabular-storage/assertions/subscribeToChanges.d.ts +21 -0
- package/dist/tabular-storage/assertions/vectorColumnFormat.d.ts +7 -0
- package/dist/tabular-storage/assertions/withConnectionTransaction.d.ts +7 -0
- package/dist/tabular-storage/assertions/withTransactionRollback.d.ts +7 -0
- package/dist/tabular-storage/fixtures.d.ts +33 -0
- package/dist/tabular-storage/joinFixtures.d.ts +66 -0
- package/dist/tabular-storage/runTabularJoinContract.d.ts +24 -0
- package/dist/tabular-storage/runTabularStorageContract.d.ts +9 -0
- package/dist/tabular-storage/types.d.ts +102 -0
- package/dist/tabular-storage.d.ts +25 -0
- package/dist/tabular-storage.js +1477 -0
- package/dist/tabular-storage.js.map +27 -0
- package/dist/vector-storage/assertions/dimensionValidation.d.ts +17 -0
- package/dist/vector-storage/assertions/legacyEncoding.d.ts +16 -0
- package/dist/vector-storage/assertions/metadataFilter.d.ts +16 -0
- package/dist/vector-storage/assertions/searchOptions.d.ts +16 -0
- package/dist/vector-storage/assertions/shared.d.ts +23 -0
- package/dist/vector-storage/assertions/similarityRanking.d.ts +18 -0
- package/dist/vector-storage/assertions/similaritySearchEvent.d.ts +16 -0
- package/dist/vector-storage/assertions/vectorRoundTrip.d.ts +17 -0
- package/dist/vector-storage/runVectorStorageContract.d.ts +20 -0
- package/dist/vector-storage/types.d.ts +87 -0
- package/dist/vector-storage.d.ts +19 -0
- package/dist/vector-storage.js +412 -0
- package/dist/vector-storage.js.map +21 -0
- package/dist/web-search/assertions/capabilityAgreement.d.ts +23 -0
- package/dist/web-search/assertions/credentialNaming.d.ts +21 -0
- package/dist/web-search/assertions/dateFiltering.d.ts +22 -0
- package/dist/web-search/assertions/domainNormalization.d.ts +24 -0
- package/dist/web-search/assertions/maxResultsCeiling.d.ts +22 -0
- package/dist/web-search/assertions/reportedQuery.d.ts +18 -0
- package/dist/web-search/assertions/signalThreading.d.ts +20 -0
- package/dist/web-search/fixtures.d.ts +40 -0
- package/dist/web-search/runWebSearchProviderConformance.d.ts +24 -0
- package/dist/web-search/types.d.ts +79 -0
- package/dist/web-search.d.ts +19 -0
- package/dist/web-search.js +560 -0
- package/dist/web-search.js.map +18 -0
- package/dist/worker-proxy/assertions/backlogOrdering.d.ts +7 -0
- package/dist/worker-proxy/assertions/disposeTerminatesWorker.d.ts +8 -0
- package/dist/worker-proxy/assertions/errorPropagation.d.ts +7 -0
- package/dist/worker-proxy/assertions/providerCallHelpers.d.ts +23 -0
- package/dist/worker-proxy/browserOnlyStub.d.ts +7 -0
- package/dist/worker-proxy/runWorkerProxyBoundary.d.ts +7 -0
- package/dist/worker-proxy/types.d.ts +47 -0
- package/dist/worker-proxy.d.ts +16 -0
- package/dist/worker-proxy.js +389 -0
- package/dist/worker-proxy.js.map +17 -0
- package/package.json +120 -0
|
@@ -0,0 +1,567 @@
|
|
|
1
|
+
// src/browser-context/ConformanceMockContext.ts
|
|
2
|
+
function parseFixturePage(url) {
|
|
3
|
+
let html = "";
|
|
4
|
+
if (url.startsWith("data:text/html,")) {
|
|
5
|
+
html = decodeURIComponent(url.slice("data:text/html,".length));
|
|
6
|
+
}
|
|
7
|
+
const titleMatch = /<title>([^<]*)<\/title>/.exec(html);
|
|
8
|
+
const buttons = [];
|
|
9
|
+
const buttonRe = /<button[^>]*aria-label="([^"]*)"[^>]*>/g;
|
|
10
|
+
let m;
|
|
11
|
+
let idx = 0;
|
|
12
|
+
while ((m = buttonRe.exec(html)) !== null) {
|
|
13
|
+
const decoded = m[1].replace(/"/g, '"').replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&");
|
|
14
|
+
buttons.push({ idx, name: decoded });
|
|
15
|
+
idx++;
|
|
16
|
+
}
|
|
17
|
+
const fetchMatch = /fetch\("data:text\/plain,([^"]+)"/.exec(html);
|
|
18
|
+
const consoleMatch = /console\.log\("([^"]+)"\)/.exec(html);
|
|
19
|
+
return {
|
|
20
|
+
title: titleMatch ? titleMatch[1] : "",
|
|
21
|
+
ariaButtons: buttons,
|
|
22
|
+
networkMarker: fetchMatch?.[1],
|
|
23
|
+
consoleMarker: consoleMatch?.[1]
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
class ConformanceMockContext {
|
|
28
|
+
connected = false;
|
|
29
|
+
nextTabSeq = 1;
|
|
30
|
+
nextRefSeq = 1;
|
|
31
|
+
pages = [];
|
|
32
|
+
activeTabId;
|
|
33
|
+
refMap = new Map;
|
|
34
|
+
async connect(_options) {
|
|
35
|
+
this.connected = true;
|
|
36
|
+
if (this.pages.length === 0) {
|
|
37
|
+
const page = this.makePage("about:blank", "");
|
|
38
|
+
this.pages.push(page);
|
|
39
|
+
this.activeTabId = page.tabId;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async disconnect() {
|
|
43
|
+
this.connected = false;
|
|
44
|
+
this.pages = [];
|
|
45
|
+
this.activeTabId = undefined;
|
|
46
|
+
this.refMap.clear();
|
|
47
|
+
}
|
|
48
|
+
isConnected() {
|
|
49
|
+
return this.connected;
|
|
50
|
+
}
|
|
51
|
+
makePage(url, title) {
|
|
52
|
+
return {
|
|
53
|
+
tabId: `mock-tab-${this.nextTabSeq++}`,
|
|
54
|
+
url,
|
|
55
|
+
title,
|
|
56
|
+
ariaButtons: [],
|
|
57
|
+
sentinelClicked: "",
|
|
58
|
+
network: [],
|
|
59
|
+
console: []
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
active() {
|
|
63
|
+
const p = this.pages.find((p) => p.tabId === this.activeTabId);
|
|
64
|
+
if (!p)
|
|
65
|
+
throw new Error("ConformanceMockContext: no active tab");
|
|
66
|
+
return p;
|
|
67
|
+
}
|
|
68
|
+
async navigate(url, _options) {
|
|
69
|
+
const page = this.active();
|
|
70
|
+
page.url = url;
|
|
71
|
+
const parsed = parseFixturePage(url);
|
|
72
|
+
page.title = parsed.title;
|
|
73
|
+
page.ariaButtons = parsed.ariaButtons;
|
|
74
|
+
page.sentinelClicked = "";
|
|
75
|
+
if (parsed.networkMarker) {
|
|
76
|
+
page.network.push({
|
|
77
|
+
url: `data:text/plain,${parsed.networkMarker}`,
|
|
78
|
+
method: "GET",
|
|
79
|
+
status: 200,
|
|
80
|
+
headers: {}
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
if (parsed.consoleMarker) {
|
|
84
|
+
page.console.push({ type: "log", text: parsed.consoleMarker });
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
async goBack(_options) {}
|
|
88
|
+
async goForward(_options) {}
|
|
89
|
+
async reload(_options) {}
|
|
90
|
+
async currentUrl() {
|
|
91
|
+
return this.active().url;
|
|
92
|
+
}
|
|
93
|
+
async title() {
|
|
94
|
+
return this.active().title;
|
|
95
|
+
}
|
|
96
|
+
async snapshot(_options) {
|
|
97
|
+
const page = this.active();
|
|
98
|
+
const children = page.ariaButtons.map((b) => {
|
|
99
|
+
const ref = `e${this.nextRefSeq++}`;
|
|
100
|
+
this.refMap.set(ref, { tabId: page.tabId, name: b.name, idx: b.idx });
|
|
101
|
+
return { ref, role: "button", name: b.name };
|
|
102
|
+
});
|
|
103
|
+
const rootRef = `e${this.nextRefSeq++}`;
|
|
104
|
+
return {
|
|
105
|
+
root: { ref: rootRef, role: "document", name: page.title, children },
|
|
106
|
+
yaml: children.map((c) => `- button [${c.ref}]: ${c.name}`).join(`
|
|
107
|
+
`)
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
async click(ref, _options) {
|
|
111
|
+
const entry = this.refMap.get(ref);
|
|
112
|
+
if (!entry)
|
|
113
|
+
throw new Error(`ConformanceMockContext: unknown ref "${ref}"`);
|
|
114
|
+
const page = this.pages.find((p) => p.tabId === entry.tabId);
|
|
115
|
+
if (!page)
|
|
116
|
+
throw new Error(`ConformanceMockContext: tab gone for ref "${ref}"`);
|
|
117
|
+
page.sentinelClicked = String(entry.idx);
|
|
118
|
+
}
|
|
119
|
+
async fill(_ref, _value, _options) {}
|
|
120
|
+
async selectOption(_ref, _values, _options) {}
|
|
121
|
+
async hover(_ref, _options) {}
|
|
122
|
+
async clickByRole(role, name, _options) {
|
|
123
|
+
if (role !== "button") {
|
|
124
|
+
throw new Error(`ConformanceMockContext: unsupported role "${role}"`);
|
|
125
|
+
}
|
|
126
|
+
const page = this.active();
|
|
127
|
+
const btn = page.ariaButtons.find((b) => b.name === name);
|
|
128
|
+
if (!btn)
|
|
129
|
+
throw new Error(`ConformanceMockContext: no button named ${JSON.stringify(name)}`);
|
|
130
|
+
page.sentinelClicked = String(btn.idx);
|
|
131
|
+
}
|
|
132
|
+
async fillByLabel(_label, _value, _options) {}
|
|
133
|
+
async content() {
|
|
134
|
+
return "";
|
|
135
|
+
}
|
|
136
|
+
async innerHTML(_ref) {
|
|
137
|
+
return "";
|
|
138
|
+
}
|
|
139
|
+
async textContent(ref) {
|
|
140
|
+
const entry = this.refMap.get(ref);
|
|
141
|
+
return entry ? entry.name : null;
|
|
142
|
+
}
|
|
143
|
+
async attribute(ref, name) {
|
|
144
|
+
if (name === "data-clicked" && ref.startsWith("sentinel-")) {
|
|
145
|
+
const tabId = ref.slice("sentinel-".length);
|
|
146
|
+
const page = this.pages.find((p) => p.tabId === tabId);
|
|
147
|
+
return page ? page.sentinelClicked : null;
|
|
148
|
+
}
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
async querySelector(selector) {
|
|
152
|
+
if (selector === "#sentinel") {
|
|
153
|
+
const page = this.active();
|
|
154
|
+
return `sentinel-${page.tabId}`;
|
|
155
|
+
}
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
async querySelectorAll(_selector) {
|
|
159
|
+
return [];
|
|
160
|
+
}
|
|
161
|
+
async evaluate(expression) {
|
|
162
|
+
if (expression.includes("readyState"))
|
|
163
|
+
return true;
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
async screenshot(_options) {
|
|
167
|
+
return Buffer.alloc(0);
|
|
168
|
+
}
|
|
169
|
+
async pressKey(_key, _options) {}
|
|
170
|
+
async type(_text, _options) {}
|
|
171
|
+
async scroll(_x, _y, _ref) {}
|
|
172
|
+
async uploadFile(_ref, _paths) {}
|
|
173
|
+
async download(trigger, _options) {
|
|
174
|
+
await trigger();
|
|
175
|
+
return { path: "", suggestedFilename: "" };
|
|
176
|
+
}
|
|
177
|
+
onDialog(_handler) {}
|
|
178
|
+
async tabs() {
|
|
179
|
+
return this.pages.map((p) => ({ tabId: p.tabId, url: p.url, title: p.title }));
|
|
180
|
+
}
|
|
181
|
+
async switchTab(tabId) {
|
|
182
|
+
if (!this.pages.some((p) => p.tabId === tabId)) {
|
|
183
|
+
throw new Error(`ConformanceMockContext: no tab "${tabId}"`);
|
|
184
|
+
}
|
|
185
|
+
this.activeTabId = tabId;
|
|
186
|
+
}
|
|
187
|
+
async newTab(url) {
|
|
188
|
+
const page = this.makePage(url ?? "about:blank", "");
|
|
189
|
+
this.pages.push(page);
|
|
190
|
+
if (url) {
|
|
191
|
+
const prev = this.activeTabId;
|
|
192
|
+
this.activeTabId = page.tabId;
|
|
193
|
+
await this.navigate(url);
|
|
194
|
+
this.activeTabId = prev;
|
|
195
|
+
}
|
|
196
|
+
return { tabId: page.tabId, url: page.url, title: page.title };
|
|
197
|
+
}
|
|
198
|
+
async closeTab(tabId) {
|
|
199
|
+
const before = this.pages.length;
|
|
200
|
+
this.pages = this.pages.filter((p) => p.tabId !== tabId);
|
|
201
|
+
if (this.pages.length === before) {
|
|
202
|
+
throw new Error(`ConformanceMockContext: no tab "${tabId}"`);
|
|
203
|
+
}
|
|
204
|
+
if (this.activeTabId === tabId) {
|
|
205
|
+
this.activeTabId = this.pages[this.pages.length - 1]?.tabId;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
async waitForNavigation(_options) {}
|
|
209
|
+
async waitForSelector(_selector, _options) {
|
|
210
|
+
return "e0";
|
|
211
|
+
}
|
|
212
|
+
async waitForIdle(_options) {}
|
|
213
|
+
}
|
|
214
|
+
// src/browser-context/fixtures.ts
|
|
215
|
+
var ARIA_EDGE_CASE_NAMES = [
|
|
216
|
+
"Sign in",
|
|
217
|
+
"foo:bar",
|
|
218
|
+
"11:30",
|
|
219
|
+
"a",
|
|
220
|
+
"héllo→",
|
|
221
|
+
"x".repeat(200)
|
|
222
|
+
];
|
|
223
|
+
var NETWORK_MARKER = "fixture-network-marker";
|
|
224
|
+
var CONSOLE_MARKER = "fixture-console-marker";
|
|
225
|
+
function escapeForHtmlAttr(text) {
|
|
226
|
+
return text.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
227
|
+
}
|
|
228
|
+
function buildPageHtml() {
|
|
229
|
+
const buttons = ARIA_EDGE_CASE_NAMES.map((name, idx) => `<button type="button" aria-label="${escapeForHtmlAttr(name)}" ` + `onclick="document.getElementById('sentinel').setAttribute('data-clicked', '${idx}')">` + escapeForHtmlAttr(name) + `</button>`).join("");
|
|
230
|
+
const head = '<head><meta charset="utf-8"><title>IBrowserContext Conformance</title></head>';
|
|
231
|
+
const body = `<body>` + `<div id="sentinel" data-clicked=""></div>` + buttons + `<script>` + `console.log(${JSON.stringify(CONSOLE_MARKER)});` + `fetch("data:text/plain,${NETWORK_MARKER}").catch(function(){});` + `</script>` + `</body>`;
|
|
232
|
+
return `<!DOCTYPE html><html>${head}${body}</html>`;
|
|
233
|
+
}
|
|
234
|
+
var PAGE_URL = `data:text/html,${encodeURIComponent(buildPageHtml())}`;
|
|
235
|
+
var DEFAULT_FIXTURE = {
|
|
236
|
+
pageUrl: PAGE_URL,
|
|
237
|
+
networkMarkerUrl: NETWORK_MARKER,
|
|
238
|
+
consoleMarker: CONSOLE_MARKER,
|
|
239
|
+
ariaEdgeCaseNames: ARIA_EDGE_CASE_NAMES
|
|
240
|
+
};
|
|
241
|
+
function resolveFixture(override) {
|
|
242
|
+
if (!override)
|
|
243
|
+
return DEFAULT_FIXTURE;
|
|
244
|
+
return { ...DEFAULT_FIXTURE, ...override };
|
|
245
|
+
}
|
|
246
|
+
// src/browser-context/runIBrowserContextConformance.ts
|
|
247
|
+
import { describe as describe5 } from "vitest";
|
|
248
|
+
|
|
249
|
+
// src/browser-context/assertions/ariaRoundTrip.ts
|
|
250
|
+
import { afterAll, beforeAll, describe, expect, it as it2 } from "vitest";
|
|
251
|
+
|
|
252
|
+
// src/browser-context/assertions/itExpectFail.ts
|
|
253
|
+
import { it } from "vitest";
|
|
254
|
+
var itExpectFail = (name, fn, timeout) => {
|
|
255
|
+
const native = it.fails;
|
|
256
|
+
if (typeof native === "function") {
|
|
257
|
+
native(name, fn, timeout);
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
it(`${name} [expected-fail]`, async () => {
|
|
261
|
+
let passed = false;
|
|
262
|
+
try {
|
|
263
|
+
await fn();
|
|
264
|
+
passed = true;
|
|
265
|
+
} catch {}
|
|
266
|
+
if (passed) {
|
|
267
|
+
throw new Error(`Test "${name}" was marked as expected-fail but passed. Remove its name from opts.expectedFailures.`);
|
|
268
|
+
}
|
|
269
|
+
}, timeout);
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
// src/browser-context/assertions/ariaRoundTrip.ts
|
|
273
|
+
function collectButtons(root) {
|
|
274
|
+
const out = [];
|
|
275
|
+
const stack = [root];
|
|
276
|
+
while (stack.length > 0) {
|
|
277
|
+
const n = stack.pop();
|
|
278
|
+
if (n.role === "button")
|
|
279
|
+
out.push(n);
|
|
280
|
+
if (n.children)
|
|
281
|
+
stack.push(...n.children);
|
|
282
|
+
}
|
|
283
|
+
return out;
|
|
284
|
+
}
|
|
285
|
+
function ariaRoundTripBlock(opts, fixture) {
|
|
286
|
+
if (!opts.capabilities.ariaSnapshot)
|
|
287
|
+
return;
|
|
288
|
+
describe("ARIA round-trip", () => {
|
|
289
|
+
const expectFail = (name) => opts.expectedFailures?.includes(name) ?? false;
|
|
290
|
+
let ctx;
|
|
291
|
+
let handle;
|
|
292
|
+
beforeAll(async () => {
|
|
293
|
+
handle = await opts.factory();
|
|
294
|
+
ctx = await handle.create();
|
|
295
|
+
await ctx.navigate(fixture.pageUrl);
|
|
296
|
+
await ctx.waitForIdle({ timeout: 5000 });
|
|
297
|
+
}, opts.timeout);
|
|
298
|
+
afterAll(async () => {
|
|
299
|
+
if (handle && ctx)
|
|
300
|
+
await handle.dispose(ctx);
|
|
301
|
+
});
|
|
302
|
+
for (let i = 0;i < fixture.ariaEdgeCaseNames.length; i++) {
|
|
303
|
+
const name = fixture.ariaEdgeCaseNames[i];
|
|
304
|
+
const isColonName = name.includes(":");
|
|
305
|
+
const title = `clickByRole('button', ${JSON.stringify(name)}) lands on the right node`;
|
|
306
|
+
const body = async () => {
|
|
307
|
+
if (!ctx)
|
|
308
|
+
throw new Error("context not created");
|
|
309
|
+
const tree = await ctx.snapshot();
|
|
310
|
+
const buttons = collectButtons(tree.root);
|
|
311
|
+
const target = buttons.find((b) => b.name === name);
|
|
312
|
+
expect(target, `snapshot must include button with name ${JSON.stringify(name)}`).toBeDefined();
|
|
313
|
+
await ctx.clickByRole("button", name);
|
|
314
|
+
const sentinelRef = await ctx.querySelector("#sentinel");
|
|
315
|
+
expect(sentinelRef).not.toBeNull();
|
|
316
|
+
const clicked = await ctx.attribute(sentinelRef, "data-clicked");
|
|
317
|
+
expect(clicked).toBe(String(i));
|
|
318
|
+
};
|
|
319
|
+
const useExpectFail = isColonName && expectFail("aria.colonInName");
|
|
320
|
+
if (useExpectFail) {
|
|
321
|
+
itExpectFail(title, body, opts.timeout);
|
|
322
|
+
} else {
|
|
323
|
+
it2(title, body, opts.timeout);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
it2("refs from snapshot N remain usable for textContent after snapshot N+1", async () => {
|
|
327
|
+
if (!ctx)
|
|
328
|
+
throw new Error("context not created");
|
|
329
|
+
const t1 = await ctx.snapshot();
|
|
330
|
+
const buttons1 = collectButtons(t1.root);
|
|
331
|
+
expect(buttons1.length).toBeGreaterThan(0);
|
|
332
|
+
const ref = buttons1[0].ref;
|
|
333
|
+
await ctx.snapshot();
|
|
334
|
+
const txt = await ctx.textContent(ref);
|
|
335
|
+
expect(txt, "ref from prior snapshot should still resolve").not.toBeNull();
|
|
336
|
+
}, opts.timeout);
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// src/browser-context/assertions/capabilityHonesty.ts
|
|
341
|
+
import { afterAll as afterAll2, beforeAll as beforeAll2, describe as describe2, expect as expect2, it as it3 } from "vitest";
|
|
342
|
+
function capabilityHonestyBlock(opts, fixture) {
|
|
343
|
+
describe2("Capability honesty", () => {
|
|
344
|
+
const expectFail = (name) => opts.expectedFailures?.includes(name) ?? false;
|
|
345
|
+
let ctx;
|
|
346
|
+
let handle;
|
|
347
|
+
beforeAll2(async () => {
|
|
348
|
+
handle = await opts.factory();
|
|
349
|
+
ctx = await handle.create();
|
|
350
|
+
}, opts.timeout);
|
|
351
|
+
afterAll2(async () => {
|
|
352
|
+
if (handle && ctx)
|
|
353
|
+
await handle.dispose(ctx);
|
|
354
|
+
});
|
|
355
|
+
const runNegative = (methodName, assertionName) => {
|
|
356
|
+
const body = async () => {
|
|
357
|
+
if (!ctx)
|
|
358
|
+
throw new Error("context not created");
|
|
359
|
+
expect2(typeof ctx[methodName]).toBe("undefined");
|
|
360
|
+
};
|
|
361
|
+
const title = `declares ${methodName}=false → ctx.${methodName} is undefined`;
|
|
362
|
+
if (expectFail(assertionName)) {
|
|
363
|
+
itExpectFail(title, body, opts.timeout);
|
|
364
|
+
} else {
|
|
365
|
+
it3(title, body, opts.timeout);
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
if (!opts.capabilities.networkRequests) {
|
|
369
|
+
runNegative("networkRequests", "capability.networkRequests.undefinedWhenFalse");
|
|
370
|
+
}
|
|
371
|
+
if (!opts.capabilities.consoleMessages) {
|
|
372
|
+
runNegative("consoleMessages", "capability.consoleMessages.undefinedWhenFalse");
|
|
373
|
+
}
|
|
374
|
+
if (opts.capabilities.networkRequests) {
|
|
375
|
+
it3("declares networkRequests=true → fixture fetch is observable", async () => {
|
|
376
|
+
if (!ctx)
|
|
377
|
+
throw new Error("context not created");
|
|
378
|
+
if (typeof ctx.networkRequests !== "function") {
|
|
379
|
+
throw new Error("capability claims networkRequests=true but ctx.networkRequests is not a function");
|
|
380
|
+
}
|
|
381
|
+
await ctx.navigate(fixture.pageUrl);
|
|
382
|
+
await ctx.waitForIdle({ timeout: 5000 });
|
|
383
|
+
const entries = await ctx.networkRequests();
|
|
384
|
+
const found = entries.some((r) => r.url.includes(fixture.networkMarkerUrl));
|
|
385
|
+
expect2(found, `expected an entry containing ${fixture.networkMarkerUrl}`).toBe(true);
|
|
386
|
+
}, opts.timeout);
|
|
387
|
+
}
|
|
388
|
+
if (opts.capabilities.consoleMessages) {
|
|
389
|
+
it3("declares consoleMessages=true → fixture console.log is observable", async () => {
|
|
390
|
+
if (!ctx)
|
|
391
|
+
throw new Error("context not created");
|
|
392
|
+
if (typeof ctx.consoleMessages !== "function") {
|
|
393
|
+
throw new Error("capability claims consoleMessages=true but ctx.consoleMessages is not a function");
|
|
394
|
+
}
|
|
395
|
+
await ctx.navigate(fixture.pageUrl);
|
|
396
|
+
await ctx.waitForIdle({ timeout: 5000 });
|
|
397
|
+
const entries = await ctx.consoleMessages();
|
|
398
|
+
const found = entries.some((m) => m.text.includes(fixture.consoleMarker));
|
|
399
|
+
expect2(found, `expected a console message containing ${fixture.consoleMarker}`).toBe(true);
|
|
400
|
+
}, opts.timeout);
|
|
401
|
+
}
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// src/browser-context/assertions/networkIntrospection.ts
|
|
406
|
+
import { afterAll as afterAll3, beforeAll as beforeAll3, describe as describe3, expect as expect3, it as it4 } from "vitest";
|
|
407
|
+
function networkIntrospectionBlock(opts, fixture) {
|
|
408
|
+
if (!opts.capabilities.networkRequests && !opts.capabilities.consoleMessages)
|
|
409
|
+
return;
|
|
410
|
+
describe3("Network/console introspection", () => {
|
|
411
|
+
let ctx;
|
|
412
|
+
let handle;
|
|
413
|
+
beforeAll3(async () => {
|
|
414
|
+
handle = await opts.factory();
|
|
415
|
+
ctx = await handle.create();
|
|
416
|
+
await ctx.navigate(fixture.pageUrl);
|
|
417
|
+
await ctx.waitForIdle({ timeout: 5000 });
|
|
418
|
+
}, opts.timeout);
|
|
419
|
+
afterAll3(async () => {
|
|
420
|
+
if (handle && ctx)
|
|
421
|
+
await handle.dispose(ctx);
|
|
422
|
+
});
|
|
423
|
+
if (opts.capabilities.networkRequests) {
|
|
424
|
+
it4("networkRequests() captures the fixture's outbound fetch", async () => {
|
|
425
|
+
if (!ctx || typeof ctx.networkRequests !== "function") {
|
|
426
|
+
throw new Error("networkRequests not available despite capability=true");
|
|
427
|
+
}
|
|
428
|
+
const entries = await ctx.networkRequests();
|
|
429
|
+
const found = entries.some((r) => r.url.includes(fixture.networkMarkerUrl));
|
|
430
|
+
expect3(found, `expected an entry containing ${fixture.networkMarkerUrl}`).toBe(true);
|
|
431
|
+
}, opts.timeout);
|
|
432
|
+
it4("networkRequests({ method }) filters by method", async () => {
|
|
433
|
+
if (!ctx || typeof ctx.networkRequests !== "function") {
|
|
434
|
+
throw new Error("networkRequests not available despite capability=true");
|
|
435
|
+
}
|
|
436
|
+
const all = await ctx.networkRequests();
|
|
437
|
+
const gets = await ctx.networkRequests({ method: "GET" });
|
|
438
|
+
expect3(gets.length).toBeLessThanOrEqual(all.length);
|
|
439
|
+
}, opts.timeout);
|
|
440
|
+
}
|
|
441
|
+
if (opts.capabilities.consoleMessages) {
|
|
442
|
+
it4("consoleMessages() captures the fixture's console.log", async () => {
|
|
443
|
+
if (!ctx || typeof ctx.consoleMessages !== "function") {
|
|
444
|
+
throw new Error("consoleMessages not available despite capability=true");
|
|
445
|
+
}
|
|
446
|
+
const entries = await ctx.consoleMessages();
|
|
447
|
+
const found = entries.some((m) => m.text.includes(fixture.consoleMarker));
|
|
448
|
+
expect3(found, `expected a console message containing ${fixture.consoleMarker}`).toBe(true);
|
|
449
|
+
}, opts.timeout);
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// src/browser-context/assertions/tabsLifecycle.ts
|
|
455
|
+
import { afterAll as afterAll4, beforeAll as beforeAll4, describe as describe4, expect as expect4, it as it5 } from "vitest";
|
|
456
|
+
function tabsLifecycleBlock(opts, fixture) {
|
|
457
|
+
describe4("Tabs lifecycle", () => {
|
|
458
|
+
const expectFail = (name) => opts.expectedFailures?.includes(name) ?? false;
|
|
459
|
+
let ctx;
|
|
460
|
+
let handle;
|
|
461
|
+
beforeAll4(async () => {
|
|
462
|
+
handle = await opts.factory();
|
|
463
|
+
ctx = await handle.create();
|
|
464
|
+
}, opts.timeout);
|
|
465
|
+
afterAll4(async () => {
|
|
466
|
+
if (handle && ctx)
|
|
467
|
+
await handle.dispose(ctx);
|
|
468
|
+
});
|
|
469
|
+
it5("tabs() returns at least one tab after connect", async () => {
|
|
470
|
+
if (!ctx)
|
|
471
|
+
throw new Error("context not created");
|
|
472
|
+
const tabs = await ctx.tabs();
|
|
473
|
+
expect4(tabs.length).toBeGreaterThanOrEqual(1);
|
|
474
|
+
}, opts.timeout);
|
|
475
|
+
if (opts.capabilities.multipleTabs) {
|
|
476
|
+
it5("newTab() increases tab count by 1", async () => {
|
|
477
|
+
if (!ctx)
|
|
478
|
+
throw new Error("context not created");
|
|
479
|
+
const before = (await ctx.tabs()).length;
|
|
480
|
+
await ctx.newTab(fixture.pageUrl);
|
|
481
|
+
const after = (await ctx.tabs()).length;
|
|
482
|
+
expect4(after).toBe(before + 1);
|
|
483
|
+
}, opts.timeout);
|
|
484
|
+
}
|
|
485
|
+
if (opts.capabilities.multipleTabs) {
|
|
486
|
+
const title = "tabId remains valid for surviving tabs across concurrent close()";
|
|
487
|
+
const body = async () => {
|
|
488
|
+
if (!ctx)
|
|
489
|
+
throw new Error("context not created");
|
|
490
|
+
const urls = [
|
|
491
|
+
"data:text/html,<title>A</title>",
|
|
492
|
+
"data:text/html,<title>B</title>",
|
|
493
|
+
"data:text/html,<title>C</title>",
|
|
494
|
+
"data:text/html,<title>D</title>"
|
|
495
|
+
];
|
|
496
|
+
for (const t of await ctx.tabs()) {
|
|
497
|
+
try {
|
|
498
|
+
await ctx.closeTab(t.tabId);
|
|
499
|
+
} catch {}
|
|
500
|
+
}
|
|
501
|
+
if ((await ctx.tabs()).length === 0) {
|
|
502
|
+
await ctx.newTab(urls[0]);
|
|
503
|
+
}
|
|
504
|
+
const baseTabs = await ctx.tabs();
|
|
505
|
+
const opened = [];
|
|
506
|
+
for (let i = 0;i < urls.length; i++) {
|
|
507
|
+
if (i < baseTabs.length) {
|
|
508
|
+
await ctx.switchTab(baseTabs[i].tabId);
|
|
509
|
+
await ctx.navigate(urls[i]);
|
|
510
|
+
opened.push(baseTabs[i].tabId);
|
|
511
|
+
} else {
|
|
512
|
+
const t = await ctx.newTab(urls[i]);
|
|
513
|
+
opened.push(t.tabId);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
const [aId, bId, cId, dId] = opened;
|
|
517
|
+
await Promise.all([ctx.closeTab(aId), ctx.closeTab(bId)]);
|
|
518
|
+
await ctx.switchTab(cId);
|
|
519
|
+
expect4(await ctx.currentUrl()).toBe(urls[2]);
|
|
520
|
+
await ctx.switchTab(dId);
|
|
521
|
+
expect4(await ctx.currentUrl()).toBe(urls[3]);
|
|
522
|
+
};
|
|
523
|
+
if (expectFail("tabs.concurrentCloseStable")) {
|
|
524
|
+
itExpectFail(title, body, opts.timeout);
|
|
525
|
+
} else {
|
|
526
|
+
it5(title, body, opts.timeout);
|
|
527
|
+
}
|
|
528
|
+
} else {
|
|
529
|
+
it5("single-view: closeTab on the sole tab disconnects or no-ops", async () => {
|
|
530
|
+
if (!ctx)
|
|
531
|
+
throw new Error("context not created");
|
|
532
|
+
const [t] = await ctx.tabs();
|
|
533
|
+
try {
|
|
534
|
+
await ctx.closeTab(t.tabId);
|
|
535
|
+
} catch {}
|
|
536
|
+
if (ctx.isConnected()) {
|
|
537
|
+
const after = await ctx.tabs();
|
|
538
|
+
expect4(after.length).toBeLessThanOrEqual(1);
|
|
539
|
+
}
|
|
540
|
+
}, opts.timeout);
|
|
541
|
+
}
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// src/browser-context/runIBrowserContextConformance.ts
|
|
546
|
+
function runIBrowserContextConformance(opts) {
|
|
547
|
+
describe5.skipIf(opts.skip)(`IBrowserContext conformance: ${opts.name}`, () => {
|
|
548
|
+
const fixture = resolveFixture(opts.fixture);
|
|
549
|
+
capabilityHonestyBlock(opts, fixture);
|
|
550
|
+
tabsLifecycleBlock(opts, fixture);
|
|
551
|
+
ariaRoundTripBlock(opts, fixture);
|
|
552
|
+
networkIntrospectionBlock(opts, fixture);
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
export {
|
|
556
|
+
ConformanceMockContext,
|
|
557
|
+
DEFAULT_FIXTURE,
|
|
558
|
+
ariaRoundTripBlock,
|
|
559
|
+
capabilityHonestyBlock,
|
|
560
|
+
itExpectFail,
|
|
561
|
+
networkIntrospectionBlock,
|
|
562
|
+
resolveFixture,
|
|
563
|
+
runIBrowserContextConformance,
|
|
564
|
+
tabsLifecycleBlock
|
|
565
|
+
};
|
|
566
|
+
|
|
567
|
+
//# debugId=CFA08057753935AD64756E2164756E21
|