@skyramp/mcp 0.4.2 → 0.4.3-rc.2
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/build/execution/wrapperConfig.d.ts +4 -0
- package/build/execution/wrapperConfig.js +14 -2
- package/build/prompts/code-reuse.d.ts +5 -1
- package/build/prompts/code-reuse.js +16 -10
- package/build/prompts/modularization/ui-test-modularization.js +9 -2
- package/build/prompts/pom-aware-code-reuse.d.ts +4 -1
- package/build/prompts/pom-aware-code-reuse.js +38 -6
- package/build/prompts/reuse-hand-off.d.ts +4 -0
- package/build/prompts/reuse-hand-off.js +10 -5
- package/build/prompts/shared-helper-policy.d.ts +5 -0
- package/build/prompts/shared-helper-policy.js +5 -0
- package/build/prompts/test-maintenance/driftAnalysisShared.js +2 -0
- package/build/prompts/test-maintenance/uiDriftAnalysisSections.js +2 -1
- package/build/prompts/testbot/testbot-prompts.js +33 -16
- package/build/recommendation/answers.d.ts +5 -3
- package/build/recommendation/answers.js +5 -2
- package/build/recommendation/fakeVerifyContext.d.ts +3 -0
- package/build/recommendation/fakeVerifyContext.js +16 -0
- package/build/recommendation/runVerifiers.js +2 -0
- package/build/recommendation/types.d.ts +12 -0
- package/build/recommendation/verifierContracts.d.ts +32 -2
- package/build/recommendation/verifierContracts.js +45 -4
- package/build/recommendation/verifiers/changedLinesClaimed.d.ts +17 -0
- package/build/recommendation/verifiers/changedLinesClaimed.js +88 -0
- package/build/recommendation/verifiers/coverage.js +143 -4
- package/build/recommendation/verifiers/expectedValueSourced.js +8 -0
- package/build/recommendation/verifiers/requirementSourced.js +141 -11
- package/build/services/TestGenerationService.js +4 -0
- package/build/tools/code-refactor/caller-gate.d.ts +105 -0
- package/build/tools/code-refactor/caller-gate.js +174 -0
- package/build/tools/code-refactor/changed-helper-report.d.ts +62 -0
- package/build/tools/code-refactor/changed-helper-report.js +114 -0
- package/build/tools/code-refactor/codeReuseTool.js +131 -62
- package/build/tools/code-refactor/enhanceAssertionsTool.js +2 -2
- package/build/tools/code-refactor/helper-callers.d.ts +65 -0
- package/build/tools/code-refactor/helper-callers.js +85 -0
- package/build/tools/code-refactor/modularizationTool.js +10 -0
- package/build/tools/code-refactor/pom-pass-state.d.ts +51 -0
- package/build/tools/code-refactor/pom-pass-state.js +115 -0
- package/build/tools/code-refactor/removed-modules.d.ts +33 -0
- package/build/tools/code-refactor/removed-modules.js +61 -0
- package/build/tools/code-refactor/retrofit-state.d.ts +3 -2
- package/build/tools/code-refactor/retrofit-state.js +11 -12
- package/build/tools/code-refactor/reuse-outcome.d.ts +33 -1
- package/build/tools/code-refactor/reuse-record-store.d.ts +57 -0
- package/build/tools/code-refactor/reuse-record-store.js +140 -0
- package/build/tools/code-refactor/reuse-state.d.ts +34 -9
- package/build/tools/code-refactor/reuse-state.js +140 -135
- package/build/tools/code-refactor/utils-verify-gates.js +42 -18
- package/build/tools/code-refactor/verify-gates.d.ts +4 -0
- package/build/tools/code-refactor/verify-gates.js +8 -1
- package/build/tools/generate-tests/generateContractRestTool.js +7 -7
- package/build/tools/generate-tests/generateIntegrationRestTool.js +8 -8
- package/build/tools/generate-tests/generateUIRestTool.js +3 -3
- package/build/tools/submitReportTool.js +121 -30
- package/build/tools/test-management/actionsTool.js +7 -0
- package/build/tools/test-management/registerTestPlanTool.d.ts +1 -0
- package/build/tools/test-management/registerTestPlanTool.js +71 -23
- package/build/types/RepositoryAnalysis.d.ts +20 -20
- package/build/types/ReuseOutcome.d.ts +123 -0
- package/build/types/TestExecution.d.ts +11 -0
- package/build/types/TestExecution.js +19 -0
- package/build/types/index.d.ts +1 -1
- package/build/utils/branchDiff.d.ts +1 -1
- package/build/utils/branchDiff.js +1 -1
- package/build/utils/changedRuns.d.ts +13 -0
- package/build/utils/changedRuns.js +56 -0
- package/build/utils/featureFlags.d.ts +8 -10
- package/build/utils/featureFlags.js +23 -21
- package/build/utils/normalizeSkyrampImports.d.ts +4 -1
- package/build/utils/normalizeSkyrampImports.js +24 -17
- package/build/utils/pom-scope/ownership.d.ts +19 -0
- package/build/utils/pom-scope/ownership.js +31 -0
- package/build/utils/pom-scope/pom-files.d.ts +8 -0
- package/build/utils/pom-scope/pom-files.js +8 -0
- package/build/utils/pom-scope/scoring.js +13 -1
- package/build/utils/pom-scope/strip.d.ts +10 -0
- package/build/utils/pom-scope/strip.js +9 -0
- package/build/utils/pom-verify/verify.js +13 -2
- package/build/utils/reuseRouting.d.ts +30 -2
- package/build/utils/reuseRouting.js +36 -15
- package/build/utils/testDependencyPolicy.js +4 -16
- package/build/utils/utils-verify/action-sites.d.ts +2 -2
- package/build/utils/utils-verify/allow.d.ts +1 -1
- package/build/utils/utils-verify/allow.js +15 -0
- package/build/utils/utils-verify/body-reach.d.ts +2 -2
- package/build/utils/utils-verify/body-reach.js +4 -1
- package/build/utils/utils-verify/call-sites.d.ts +16 -6
- package/build/utils/utils-verify/call-sites.js +12 -6
- package/build/utils/utils-verify/changed-helpers.d.ts +38 -0
- package/build/utils/utils-verify/changed-helpers.js +91 -0
- package/build/utils/utils-verify/changed-lines.d.ts +48 -0
- package/build/utils/utils-verify/changed-lines.js +106 -0
- package/build/utils/utils-verify/head.d.ts +73 -0
- package/build/utils/utils-verify/head.js +156 -0
- package/build/utils/utils-verify/importers.d.ts +59 -10
- package/build/utils/utils-verify/importers.js +121 -31
- package/build/utils/utils-verify/in-house.js +26 -31
- package/build/utils/utils-verify/index.d.ts +3 -0
- package/build/utils/utils-verify/index.js +3 -0
- package/build/utils/utils-verify/language-spec.js +10 -2
- package/build/utils/utils-verify/locate.d.ts +3 -0
- package/build/utils/utils-verify/locate.js +6 -6
- package/build/utils/utils-verify/parse.d.ts +13 -1
- package/build/utils/utils-verify/parse.js +122 -49
- package/build/utils/utils-verify/retrofit-equivalence.d.ts +0 -5
- package/build/utils/utils-verify/retrofit-equivalence.js +2 -34
- package/build/utils/utils-verify/stage.js +4 -16
- package/build/utils/utils-verify/typecheck.d.ts +25 -4
- package/build/utils/utils-verify/typecheck.js +95 -13
- package/build/utils/utils-verify/verify.d.ts +33 -8
- package/build/utils/utils-verify/verify.js +180 -18
- package/build/utils/versions.d.ts +3 -3
- package/build/utils/versions.js +1 -1
- package/node_modules/playwright/lib/dom-analyzer/blueprint.js +1 -0
- package/node_modules/playwright/lib/dom-analyzer/dynamicId.js +1 -0
- package/node_modules/playwright/lib/dom-analyzer/dynamicId.test.js +6 -0
- package/node_modules/playwright/lib/mcp/skyramp/exportTool.js +4 -2
- package/node_modules/playwright/lib/mcp/skyramp/snapshotLocators.js +246 -0
- package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +378 -237
- package/node_modules/playwright/lib/mcp/test/skyRampExport.js +21 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/generated/pollingRecorderSource.js +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/{codeMirrorModule-LNgEKtdV.js → codeMirrorModule-Bou8kKzE.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/{defaultSettingsView-Bwr1eMKC.js → defaultSettingsView-DTp2-8Si.js} +3 -3
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.html +2 -2
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{index.BAkLd5DX.js → index.uiPTFncY.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{uiMode.BPopbasy.js → uiMode.GLqsx5cI.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/uiMode.html +2 -2
- package/node_modules/playwright/node_modules/playwright-core/package.json +1 -1
- package/node_modules/playwright/node_modules/playwright-core/src/generated/pollingRecorderSource.ts +1 -1
- package/node_modules/playwright/package.json +1 -1
- package/package.json +3 -3
- package/plugin/prompts/code-reuse/hand-off.md +13 -1
- package/plugin/prompts/plan-tests.md +2 -2
- package/plugin/skills/enhance-assertions/reference/shared-rules.md +1 -0
|
@@ -7,19 +7,21 @@ import { typecheckDelivered } from "./typecheck.js";
|
|
|
7
7
|
import { blankComments, blankCommentsAndStrings, countAssertions, hasPageErrorGuard, parseUtilsFile, } from "./parse.js";
|
|
8
8
|
import { escapeRegExp } from "../regex.js";
|
|
9
9
|
import { detectGitRoot } from "../gitStaging.js";
|
|
10
|
-
import { inlineCallSiteFamily, isModularizeFirstTarget, } from "../reuseRouting.js";
|
|
10
|
+
import { inlineCallSiteFamily, isModularizeFirstTarget, isPomFirstTarget, } from "../reuseRouting.js";
|
|
11
11
|
import { describeActionKey } from "./action-key.js";
|
|
12
12
|
import { findDuplicateBodyReach, findUnreachableBodies } from "./body-reach.js";
|
|
13
13
|
import { misnamedNewModules } from "./module-name.js";
|
|
14
|
+
import { pageObjectCandidates } from "../pom-scope/ownership.js";
|
|
14
15
|
import { additiveChanges, locateInHouseModules, resolveInHouseCalls, } from "./in-house.js";
|
|
15
16
|
import { findSiblingInlineActionSites } from "./action-sites.js";
|
|
16
17
|
import { findSiblingInlineCallSites, findUnextractedDuplicates, generatedSiblings, } from "./call-sites.js";
|
|
17
18
|
import { parseUtilsAllows, } from "./allow.js";
|
|
18
19
|
import { describeFixedSleeps, fixedSleepReplacement } from "./fixed-sleep.js";
|
|
19
|
-
import { singleImporterHelpers
|
|
20
|
+
import { singleImporterHelpers } from "./importers.js";
|
|
20
21
|
import { assertsStatusEquality, defaultedStatusParam, pinnedStatusLiteral, restatedStatusAssertions, } from "./status-once.js";
|
|
21
22
|
import { STATUS_ONCE_RULE } from "../../prompts/shared-helper-policy.js";
|
|
22
|
-
import {
|
|
23
|
+
import { addedSinceHead, changedSinceHead, contentAtHead, readAtHead, } from "./head.js";
|
|
24
|
+
import { alignLines, BaselineErrors, baselineErrorKey, inheritedTypeError, } from "./changed-lines.js";
|
|
23
25
|
import { incumbentSignatureViolations } from "./incumbent.js";
|
|
24
26
|
import { helperFamilyFor, helperFamilyOf, scenarioNameViolation, utilsSpecFor, utilsSpecForFile, } from "./language-spec.js";
|
|
25
27
|
/** What the compiler says about a name defined twice, which `helper-redefined`
|
|
@@ -51,9 +53,11 @@ const REDECLARATION_CODES = new Set([2393, 2323, 2451]);
|
|
|
51
53
|
* returned. See status-once.ts.
|
|
52
54
|
* - `type-error` (TypeScript): the delivered module or spec fails the compiler with
|
|
53
55
|
* every strictness flag off — an error under every configuration (a path parameter
|
|
54
|
-
* typed `unknown`, request options built as a record and handed to `sendRequest`)
|
|
55
|
-
*
|
|
56
|
-
* ADVISORY
|
|
56
|
+
* typed `unknown`, request options built as a record and handed to `sendRequest`)
|
|
57
|
+
* — and this run CAUSED it: an error the file already had at HEAD on an
|
|
58
|
+
* unchanged line is the `inherited-type-error` ADVISORY, and an error only the
|
|
59
|
+
* repository's own tsconfig raises is the `tsconfig-type-error` ADVISORY. See
|
|
60
|
+
* typecheckDelivered and inheritedTypeError.
|
|
57
61
|
*
|
|
58
62
|
* Analyses the utils files' INTERNAL invariants — unlike the POM verifier, which
|
|
59
63
|
* checks a spec's cross-file references — so it reads the utils files, not the spec,
|
|
@@ -80,9 +84,16 @@ export async function verifyUtils(params) {
|
|
|
80
84
|
// locateInHouseModules). Found BEFORE the utils files so a customer module that
|
|
81
85
|
// gained our header this run is judged by the additive gate, never by the Skyramp
|
|
82
86
|
// invariants: the header locator would otherwise claim it.
|
|
83
|
-
const
|
|
87
|
+
const located = spec
|
|
84
88
|
? await locateInHouseModules({ testFile: params.testFile, spec, cwd: root })
|
|
85
89
|
: [];
|
|
90
|
+
// Both reuse settings on: a module that declares a class is a page object, which
|
|
91
|
+
// the page-object pass owned and its verifier checked. The in-house parser cannot
|
|
92
|
+
// read a class, and its additive gate would judge a page-object edit it did not
|
|
93
|
+
// prescribe — so the split is by shape, the one pom-scope/ownership.ts states.
|
|
94
|
+
const pomFirst = !!spec && isPomFirstTarget(spec.language, params.testType);
|
|
95
|
+
const pageObjectFiles = new Set(pomFirst ? (await pageObjectCandidates(located)).map((m) => m.file) : []);
|
|
96
|
+
const inHouse = located.filter((m) => !pageObjectFiles.has(m.file));
|
|
86
97
|
const inHouseFiles = new Set(inHouse.map((m) => m.file));
|
|
87
98
|
const utilsFiles = spec
|
|
88
99
|
? (await locateUtilsFiles({
|
|
@@ -94,6 +105,8 @@ export async function verifyUtils(params) {
|
|
|
94
105
|
const result = {
|
|
95
106
|
ok: true,
|
|
96
107
|
utilsFiles,
|
|
108
|
+
incumbentModules: [],
|
|
109
|
+
headUnknownModules: [],
|
|
97
110
|
helpers: 0,
|
|
98
111
|
imported: 0,
|
|
99
112
|
importedNames: [],
|
|
@@ -107,6 +120,7 @@ export async function verifyUtils(params) {
|
|
|
107
120
|
inlineCallSites: [],
|
|
108
121
|
unextractedDuplicates: [],
|
|
109
122
|
singleImporters: [],
|
|
123
|
+
importersComplete: false,
|
|
110
124
|
};
|
|
111
125
|
if (!spec)
|
|
112
126
|
return result;
|
|
@@ -127,6 +141,11 @@ export async function verifyUtils(params) {
|
|
|
127
141
|
* kept: both the status advisory's scope and the restatement's remediation
|
|
128
142
|
* turn on it. */
|
|
129
143
|
const incumbentsOfFile = new Map();
|
|
144
|
+
/** Per utils file, the module at HEAD — `undefined` for a module HEAD does not
|
|
145
|
+
* hold. The compiler's attribution reads it: an error the module already had at
|
|
146
|
+
* HEAD is not this run's. */
|
|
147
|
+
const headContentOfFile = new Map();
|
|
148
|
+
const contentOfFile = new Map();
|
|
130
149
|
const allHelpers = [];
|
|
131
150
|
const helpersByFile = new Map();
|
|
132
151
|
const allAllows = [];
|
|
@@ -161,10 +180,17 @@ export async function verifyUtils(params) {
|
|
|
161
180
|
// helpers this run writes, so a defaulted status on an incumbent is not advised
|
|
162
181
|
// — and an incumbent's signature is held to what it was (incumbent.ts). Absent
|
|
163
182
|
// for a module this run created, or outside git: then every helper is this run's.
|
|
164
|
-
const
|
|
183
|
+
const atHead = await readAtHead(file);
|
|
184
|
+
const baseline = "content" in atHead ? atHead.content : undefined;
|
|
185
|
+
if (baseline !== undefined)
|
|
186
|
+
result.incumbentModules.push(file);
|
|
187
|
+
else if ("unknown" in atHead)
|
|
188
|
+
result.headUnknownModules.push(file);
|
|
165
189
|
const headHelpers = baseline === undefined ? [] : parseUtilsFile(baseline, fileSpec);
|
|
166
190
|
const incumbents = new Set(headHelpers.map((h) => h.name));
|
|
167
191
|
incumbentsOfFile.set(file, incumbents);
|
|
192
|
+
headContentOfFile.set(file, baseline);
|
|
193
|
+
contentOfFile.set(file, content);
|
|
168
194
|
pending.push(...incumbentSignatureViolations(headHelpers, helpers, file));
|
|
169
195
|
for (const h of helpers) {
|
|
170
196
|
// A static reading of the signature. An unannotated parameter is an error only
|
|
@@ -321,6 +347,9 @@ export async function verifyUtils(params) {
|
|
|
321
347
|
}
|
|
322
348
|
if (utilsSet.has(imp.resolved) || imp.resolved === realTestFile)
|
|
323
349
|
continue;
|
|
350
|
+
// The page-object pass's module: checked by its verifier, not this one.
|
|
351
|
+
if (pageObjectFiles.has(imp.resolved))
|
|
352
|
+
continue;
|
|
324
353
|
if (inHouseFiles.has(imp.resolved)) {
|
|
325
354
|
if (!inHouseResult)
|
|
326
355
|
unchecked.push(imp.resolved);
|
|
@@ -372,7 +401,7 @@ export async function verifyUtils(params) {
|
|
|
372
401
|
if (!inHouseResult && inHouse.length > 0)
|
|
373
402
|
result.inHouseChecksSkipped = `this pass ran ${params.testType
|
|
374
403
|
? `as a ${params.testType.toLowerCase()} test`
|
|
375
|
-
: "with no test type supplied"}, and they run for a UI test with
|
|
404
|
+
: "with no test type supplied"}, and they run for a UI test in TypeScript or JavaScript with utils reuse on`;
|
|
376
405
|
}
|
|
377
406
|
// The spec asserts a status a helper it calls already asserted on the response it
|
|
378
407
|
// returned. Spec-level, like assertion-loss: the finding names the spec and the
|
|
@@ -419,8 +448,10 @@ export async function verifyUtils(params) {
|
|
|
419
448
|
// The compiler over the delivered files: the shared module(s) and the spec that
|
|
420
449
|
// imports them. The executor transpiles TypeScript without typechecking it, so
|
|
421
450
|
// this is the first typecheck the files meet before the customer's own. An error
|
|
422
|
-
// under the permissive settings fails under every configuration and blocks
|
|
423
|
-
//
|
|
451
|
+
// under the permissive settings fails under every configuration and blocks when
|
|
452
|
+
// this run caused it — one the file already had at HEAD advises (see the
|
|
453
|
+
// attribution below); one only the repository's tsconfig raises advises (see
|
|
454
|
+
// typecheckDelivered).
|
|
424
455
|
// Over the TypeScript files only, by extension: a JavaScript module is not
|
|
425
456
|
// typechecked (its spec would be `checkJs`, which no delivered file relies on),
|
|
426
457
|
// and a JavaScript spec is not compiled beside a TypeScript module.
|
|
@@ -432,7 +463,7 @@ export async function verifyUtils(params) {
|
|
|
432
463
|
const realSpec = await realpath(params.testFile);
|
|
433
464
|
const specFile = realSpec && isTs(realSpec) ? realSpec : undefined;
|
|
434
465
|
const files = specFile ? [...tsUtils, specFile] : tsUtils;
|
|
435
|
-
const tc = await typecheckDelivered(files);
|
|
466
|
+
const tc = await typecheckDelivered(files, { anchor: specFile });
|
|
436
467
|
result.typecheck = tc;
|
|
437
468
|
const subjectOf = (file, line) => file === specFile
|
|
438
469
|
? path.basename(specFile).replace(/\W/g, "_")
|
|
@@ -447,11 +478,96 @@ export async function verifyUtils(params) {
|
|
|
447
478
|
const redefined = new Set(pending
|
|
448
479
|
.filter((v) => v.kind === "helper-redefined")
|
|
449
480
|
.map((v) => `${v.file}\0${v.helper}`));
|
|
481
|
+
// The files as the repository held them BEFORE this run — each at HEAD —
|
|
482
|
+
// compiled once more, so that an error is excused only when the repository
|
|
483
|
+
// already had it on the same line: attribution by CAUSE. A file HEAD does not
|
|
484
|
+
// hold (a module or a spec this run created) is hidden from that compile and
|
|
485
|
+
// every error in it is this run's — the generator's included: a line any
|
|
486
|
+
// pass of this run wrote is the run's to fix, and only what the customer or
|
|
487
|
+
// an earlier run committed is not.
|
|
488
|
+
const baselineContents = new Map();
|
|
489
|
+
const hidden = new Set();
|
|
490
|
+
const attribution = {
|
|
491
|
+
ran: false,
|
|
492
|
+
headUnknown: [...result.headUnknownModules],
|
|
493
|
+
};
|
|
494
|
+
result.attribution = attribution;
|
|
495
|
+
for (const file of tsUtils) {
|
|
496
|
+
const head = headContentOfFile.get(file);
|
|
497
|
+
if (head === undefined)
|
|
498
|
+
hidden.add(file);
|
|
499
|
+
else
|
|
500
|
+
baselineContents.set(file, head);
|
|
501
|
+
}
|
|
502
|
+
if (specFile) {
|
|
503
|
+
const specAtHead = await readAtHead(specFile);
|
|
504
|
+
if ("content" in specAtHead)
|
|
505
|
+
baselineContents.set(specFile, specAtHead.content);
|
|
506
|
+
else {
|
|
507
|
+
hidden.add(specFile);
|
|
508
|
+
if ("unknown" in specAtHead)
|
|
509
|
+
attribution.headUnknown.push(specFile);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
// Every OTHER file the program may reach — a page object, an in-house helper,
|
|
513
|
+
// a config module — as HEAD held it too: one the run changed is served from
|
|
514
|
+
// HEAD, one the run added does not exist. Read from the working tree, a
|
|
515
|
+
// page-object edit put its error into both compiles and it was excused as
|
|
516
|
+
// inherited. Only the extensions the compiler reads; nothing under
|
|
517
|
+
// node_modules, which the run does not edit.
|
|
518
|
+
// Nothing to attribute on a clean compile: the HEAD compile — a full second
|
|
519
|
+
// program build — is skipped, and the skip is stated so the field is never
|
|
520
|
+
// read as "attribution ran and excused nothing".
|
|
521
|
+
const baselineErrors = new BaselineErrors();
|
|
522
|
+
if (tc.errors.length === 0)
|
|
523
|
+
attribution.reason = "the delivered files compiled clean";
|
|
524
|
+
else if (baselineContents.size === 0)
|
|
525
|
+
attribution.reason =
|
|
526
|
+
"no delivered file exists at HEAD, so there is nothing to inherit from";
|
|
527
|
+
else if (!(await addBaselineForChangedFiles(root, baselineContents, hidden)))
|
|
528
|
+
attribution.reason =
|
|
529
|
+
"the files this run changed could not be listed, so no file could be read at HEAD";
|
|
530
|
+
else {
|
|
531
|
+
const before = await typecheckDelivered([...baselineContents.keys()], {
|
|
532
|
+
anchor: specFile,
|
|
533
|
+
baseline: { contents: baselineContents, hidden },
|
|
534
|
+
});
|
|
535
|
+
if (!before.ran) {
|
|
536
|
+
// The delivered compile ran and this one did not: something in the HEAD
|
|
537
|
+
// content or the host. Stated, never silent — the old errors then block
|
|
538
|
+
// as this run's, which is the outcome attribution exists to prevent.
|
|
539
|
+
attribution.reason = `the HEAD compile did not run: ${before.reason ?? "unknown cause"}`;
|
|
540
|
+
logger.warning("The HEAD compile did not run — no type error will be excused as inherited", { testFile: params.testFile, reason: before.reason });
|
|
541
|
+
}
|
|
542
|
+
else {
|
|
543
|
+
attribution.ran = true;
|
|
544
|
+
for (const e of before.errors)
|
|
545
|
+
baselineErrors.add(baselineErrorKey(e.file, e.line, e.code, e.message));
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
const unchangedOf = new Map();
|
|
549
|
+
for (const [file, before] of baselineContents) {
|
|
550
|
+
const now = file === specFile ? specContent : contentOfFile.get(file);
|
|
551
|
+
if (now !== undefined)
|
|
552
|
+
unchangedOf.set(file, alignLines(before, now));
|
|
553
|
+
}
|
|
554
|
+
const inherited = inheritedTypeError((file) => unchangedOf.get(file), baselineErrors);
|
|
450
555
|
for (const e of tc.errors) {
|
|
451
556
|
const helper = subjectOf(e.file, e.line);
|
|
452
557
|
if (REDECLARATION_CODES.has(e.code) &&
|
|
453
558
|
redefined.has(`${e.file}\0${helper}`))
|
|
454
559
|
continue;
|
|
560
|
+
const why = inherited(e.file, e.line, e.code, e.message);
|
|
561
|
+
if (why !== undefined) {
|
|
562
|
+
result.advisories.push({
|
|
563
|
+
kind: "inherited-type-error",
|
|
564
|
+
file: e.file,
|
|
565
|
+
helper,
|
|
566
|
+
line: e.line,
|
|
567
|
+
detail: `\`TS${e.code}: ${e.message}\` — ${why}`,
|
|
568
|
+
});
|
|
569
|
+
continue;
|
|
570
|
+
}
|
|
455
571
|
pending.push({
|
|
456
572
|
kind: "type-error",
|
|
457
573
|
file: e.file,
|
|
@@ -478,12 +594,13 @@ export async function verifyUtils(params) {
|
|
|
478
594
|
});
|
|
479
595
|
}
|
|
480
596
|
// One directory walk feeds every sibling advisory: each sibling is read exactly once.
|
|
481
|
-
let siblings = [];
|
|
597
|
+
let siblings = { files: [], complete: false };
|
|
482
598
|
try {
|
|
483
599
|
siblings = await generatedSiblings(params.testFile, spec, {
|
|
484
600
|
includeSelf: true,
|
|
485
601
|
});
|
|
486
602
|
result.singleImporters = await singleImporterHelpers(params.testFile, utilsFiles, helpersByFile, spec, siblings);
|
|
603
|
+
result.importersComplete = siblings.complete;
|
|
487
604
|
}
|
|
488
605
|
catch {
|
|
489
606
|
/* advisory only — never fails a verify */
|
|
@@ -673,6 +790,50 @@ function remediation(helper, literal, incumbent) {
|
|
|
673
790
|
}
|
|
674
791
|
/** The compared counts as one phrase, shared by the assertion-loss detail and the
|
|
675
792
|
* PASSED line's note so the two cannot drift. */
|
|
793
|
+
/** Extensions the compiler reads as source. */
|
|
794
|
+
const COMPILED_EXTENSIONS = new Set([
|
|
795
|
+
".ts",
|
|
796
|
+
".tsx",
|
|
797
|
+
".mts",
|
|
798
|
+
".cts",
|
|
799
|
+
".js",
|
|
800
|
+
".jsx",
|
|
801
|
+
".mjs",
|
|
802
|
+
".cjs",
|
|
803
|
+
".json",
|
|
804
|
+
]);
|
|
805
|
+
/**
|
|
806
|
+
* Add to the baseline every compiled file in the repository that differs from
|
|
807
|
+
* HEAD and is not already accounted for: changed files served from HEAD, added
|
|
808
|
+
* files hidden. Returns whether the listing succeeded — a baseline that cannot
|
|
809
|
+
* be completed must excuse NOTHING, since any file it reads from disk may carry
|
|
810
|
+
* the run's own edit; the caller then compiles no baseline and every error
|
|
811
|
+
* blocks, as before attribution existed.
|
|
812
|
+
*/
|
|
813
|
+
async function addBaselineForChangedFiles(root, contents, hidden) {
|
|
814
|
+
if (!root)
|
|
815
|
+
return true; // outside git nothing has a HEAD; nothing is excused
|
|
816
|
+
const compiled = (f) => COMPILED_EXTENSIONS.has(path.extname(f)) &&
|
|
817
|
+
!f.split(path.sep).includes("node_modules");
|
|
818
|
+
try {
|
|
819
|
+
for (const file of await changedSinceHead(root, { filter: "M" })) {
|
|
820
|
+
if (!compiled(file) || contents.has(file) || hidden.has(file))
|
|
821
|
+
continue;
|
|
822
|
+
const head = await contentAtHead(file, root);
|
|
823
|
+
if (head === undefined)
|
|
824
|
+
return false; // git did not answer: excuse nothing
|
|
825
|
+
contents.set(file, head);
|
|
826
|
+
}
|
|
827
|
+
for (const file of await addedSinceHead(root))
|
|
828
|
+
if (compiled(file) && !contents.has(file))
|
|
829
|
+
hidden.add(file);
|
|
830
|
+
return true;
|
|
831
|
+
}
|
|
832
|
+
catch (err) {
|
|
833
|
+
logger.warning("Could not list the files this run changed — no type error will be excused as inherited", { root, error: String(err) });
|
|
834
|
+
return false;
|
|
835
|
+
}
|
|
836
|
+
}
|
|
676
837
|
export function describeAssertionCount(c) {
|
|
677
838
|
return `${c.baseline} assertions at the modularization hand-out, ${c.delivered} delivered (${c.inSpec} in the test, ${c.inHelpers} in the helpers it imports and calls)`;
|
|
678
839
|
}
|
|
@@ -864,9 +1025,9 @@ incumbents = new Set()) {
|
|
|
864
1025
|
});
|
|
865
1026
|
}
|
|
866
1027
|
else if (sameName &&
|
|
867
|
-
h.
|
|
868
|
-
sameName.
|
|
869
|
-
h.
|
|
1028
|
+
h.params !== undefined &&
|
|
1029
|
+
sameName.params !== undefined &&
|
|
1030
|
+
h.params.length !== sameName.params.length &&
|
|
870
1031
|
!(routeDuplicateOf?.file === sameName.file &&
|
|
871
1032
|
routeDuplicateOf?.line === sameName.line)) {
|
|
872
1033
|
nameReported = true;
|
|
@@ -875,7 +1036,7 @@ incumbents = new Set()) {
|
|
|
875
1036
|
file,
|
|
876
1037
|
helper: h.name,
|
|
877
1038
|
line: h.line,
|
|
878
|
-
detail: `${h.name} is defined in two modules with different parameter lists — ${h.
|
|
1039
|
+
detail: `${h.name} is defined in two modules with different parameter lists — ${h.params.length} parameter${h.params.length === 1 ? "" : "s"} here, ${sameName.params.length} at ${path.basename(sameName.file)}:${sameName.line}; the two modules disagree on one operation's signature, and the next test to import it gets whichever it finds first. Keep ONE definition, give every parameter the other lacked a default, and point every importer at it`,
|
|
879
1040
|
});
|
|
880
1041
|
}
|
|
881
1042
|
if (!inFile)
|
|
@@ -898,7 +1059,8 @@ incumbents = new Set()) {
|
|
|
898
1059
|
line: h.line,
|
|
899
1060
|
// Only the parameter COUNT is compared, and only when both were read.
|
|
900
1061
|
detail: first.name === h.name
|
|
901
|
-
? `${h.name} is defined twice (also at ${path.basename(first.file)}:${first.line}) with the same ${h.
|
|
1062
|
+
? `${h.name} is defined twice (also at ${path.basename(first.file)}:${first.line}) with the same ${h.params !== undefined &&
|
|
1063
|
+
h.params.length === first.params?.length
|
|
902
1064
|
? "parameter count and the same "
|
|
903
1065
|
: ""}action sequence: ${describeActionKey(h.actionKey)}; keep one definition and point every importer at it`
|
|
904
1066
|
: `${h.name} and ${first.name} (${path.basename(first.file)}:${first.line}) perform the same action sequence: ${describeActionKey(h.actionKey)}`,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const SKYRAMP_IMAGE_VERSION = "v1.3.
|
|
2
|
-
export declare const EXECUTOR_DOCKER_IMAGE = "skyramp/executor:v1.3.
|
|
3
|
-
export declare const WORKER_DOCKER_IMAGE = "skyramp/worker:v1.3.
|
|
1
|
+
export declare const SKYRAMP_IMAGE_VERSION = "v1.3.47";
|
|
2
|
+
export declare const EXECUTOR_DOCKER_IMAGE = "skyramp/executor:v1.3.47";
|
|
3
|
+
export declare const WORKER_DOCKER_IMAGE = "skyramp/worker:v1.3.47";
|
|
4
4
|
export declare const WORKER_CONTROL_PORT = 35142;
|
package/build/utils/versions.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const SKYRAMP_IMAGE_VERSION = "v1.3.
|
|
1
|
+
export const SKYRAMP_IMAGE_VERSION = "v1.3.47";
|
|
2
2
|
export const EXECUTOR_DOCKER_IMAGE = `skyramp/executor:${SKYRAMP_IMAGE_VERSION}`;
|
|
3
3
|
export const WORKER_DOCKER_IMAGE = `skyramp/worker:${SKYRAMP_IMAGE_VERSION}`;
|
|
4
4
|
// Control port the Skyramp worker listens on (SDK `CONTAINER_PORT`).
|
|
@@ -608,6 +608,7 @@ async function domEvaluationScript(options) {
|
|
|
608
608
|
if (m && /[0-9]/.test(m[1])) return true;
|
|
609
609
|
if (/[a-zA-Z][0-9]{3,}$/.test(id)) return true;
|
|
610
610
|
if (id.includes(":")) return true;
|
|
611
|
+
if (/«r[0-9a-z]*»|(^|[-_])_?r_[0-9a-z]+_/.test(id)) return true;
|
|
611
612
|
if (/^.+__search_[a-zA-Z0-9]{4,}$/.test(id)) return true;
|
|
612
613
|
return false;
|
|
613
614
|
}
|
|
@@ -34,6 +34,7 @@ function isDynamicId(id) {
|
|
|
34
34
|
if (shortHexMatch && /[0-9]/.test(shortHexMatch[1])) return true;
|
|
35
35
|
if (/[a-zA-Z][0-9]{3,}$/.test(id)) return true;
|
|
36
36
|
if (id.includes(":")) return true;
|
|
37
|
+
if (/«r[0-9a-z]*»|(^|[-_])_?r_[0-9a-z]+_/.test(id)) return true;
|
|
37
38
|
if (/^.+__search_[a-zA-Z0-9]{4,}$/.test(id)) return true;
|
|
38
39
|
return false;
|
|
39
40
|
}
|
|
@@ -65,6 +65,12 @@ test("isDynamicId: single trailing digit after letter is not dynamic", () => {
|
|
|
65
65
|
assertFalse((0, import_dynamicId.isDynamicId)("item1"), "item1 (single digit)");
|
|
66
66
|
assertFalse((0, import_dynamicId.isDynamicId)("item12"), "item12 (2 digits, below 3-digit threshold)");
|
|
67
67
|
});
|
|
68
|
+
test("isDynamicId: React 19 useId forms", () => {
|
|
69
|
+
assertTrue((0, import_dynamicId.isDynamicId)("\xABr2\xBB-form-item"), "\xABr2\xBB-form-item (React 19.0)");
|
|
70
|
+
assertTrue((0, import_dynamicId.isDynamicId)("radix-_r_16_"), "radix-_r_16_ (React 19.1 inside Radix)");
|
|
71
|
+
assertTrue((0, import_dynamicId.isDynamicId)("_r_1k_"), "_r_1k_ (bare React 19.1)");
|
|
72
|
+
assertFalse((0, import_dynamicId.isDynamicId)("user_r_name"), "user_r_name (word, not a useId)");
|
|
73
|
+
});
|
|
68
74
|
let failed = 0;
|
|
69
75
|
for (const { name, run } of cases) {
|
|
70
76
|
try {
|
|
@@ -88,10 +88,12 @@ Requested path: ${outputZip}` }],
|
|
|
88
88
|
counter++;
|
|
89
89
|
outputZip = `${base}_${counter}${ext}`;
|
|
90
90
|
}
|
|
91
|
-
const { jsonl: jsonlContent, actionCount, skipped } = (0, import_skyRampExport.buildJsonlContent)(ctx.trackedActions, "chromium", ctx.harPath);
|
|
91
|
+
const { jsonl: jsonlContent, actionCount, skipped, simplified } = (0, import_skyRampExport.buildJsonlContent)(ctx.trackedActions, "chromium", ctx.harPath);
|
|
92
92
|
await (0, import_skyRampExport.writeSkyrampZip)(outputZip, jsonlContent, ctx.harPath);
|
|
93
93
|
const skippedNote = skipped.length ? `
|
|
94
94
|
Skipped (not exportable): ${[...new Set(skipped)].join(", ")}` : "";
|
|
95
|
+
const simplifiedNote = simplified.length ? `
|
|
96
|
+
Exported with a simplified locator (a part the trace format cannot carry was dropped, so the step may match more elements at replay): ${[...new Set(simplified)].join("; ")}` : "";
|
|
95
97
|
const dropped = ctx.droppedActions ?? [];
|
|
96
98
|
const droppedNote = dropped.length ? `
|
|
97
99
|
Failed & dropped (not recorded): ${summarizeDropped(dropped)}` : "";
|
|
@@ -102,7 +104,7 @@ Failed & dropped (not recorded): ${summarizeDropped(dropped)}` : "";
|
|
|
102
104
|
Skyramp zip written to ${outputZip}
|
|
103
105
|
|
|
104
106
|
Contains:
|
|
105
|
-
- skyramp_playwright.txt (${actionCount} actions)${skippedNote}${droppedNote}`
|
|
107
|
+
- skyramp_playwright.txt (${actionCount} actions)${skippedNote}${simplifiedNote}${droppedNote}`
|
|
106
108
|
}]
|
|
107
109
|
};
|
|
108
110
|
};
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var snapshotLocators_exports = {};
|
|
20
|
+
__export(snapshotLocators_exports, {
|
|
21
|
+
exactLeaf: () => exactLeaf,
|
|
22
|
+
isInputLine: () => isInputLine,
|
|
23
|
+
isTestHookCss: () => isTestHookCss,
|
|
24
|
+
isVolatileCss: () => isVolatileCss,
|
|
25
|
+
leaf: () => leaf,
|
|
26
|
+
locatorFromSelector: () => locatorFromSelector,
|
|
27
|
+
renderSelector: () => renderSelector,
|
|
28
|
+
snapshotInputValue: () => snapshotInputValue,
|
|
29
|
+
snapshotLineText: () => snapshotLineText,
|
|
30
|
+
snapshotMatches: () => snapshotMatches,
|
|
31
|
+
unquoteYaml: () => unquoteYaml,
|
|
32
|
+
withNth: () => withNth
|
|
33
|
+
});
|
|
34
|
+
module.exports = __toCommonJS(snapshotLocators_exports);
|
|
35
|
+
var import_dynamicId = require("../../dom-analyzer/dynamicId");
|
|
36
|
+
const INPUT_ROLES = /* @__PURE__ */ new Set(["textbox", "searchbox", "combobox", "spinbutton"]);
|
|
37
|
+
function isInputLine(refLine) {
|
|
38
|
+
const role = refLine.match(/^\s*-\s*(\w+)/)?.[1];
|
|
39
|
+
return !!role && INPUT_ROLES.has(role);
|
|
40
|
+
}
|
|
41
|
+
function snapshotInputValue(snapLines, refIdx) {
|
|
42
|
+
if (refIdx < 0)
|
|
43
|
+
return "";
|
|
44
|
+
const refLine = snapLines[refIdx];
|
|
45
|
+
const inline = refLine.match(/\]:\s*(.+)$/)?.[1]?.trim();
|
|
46
|
+
if (inline)
|
|
47
|
+
return unquoteYaml(inline);
|
|
48
|
+
if (!isInputLine(refLine))
|
|
49
|
+
return "";
|
|
50
|
+
const indentOf = (line) => line.match(/^(\s*)/)?.[1]?.length ?? 0;
|
|
51
|
+
const indent = indentOf(refLine);
|
|
52
|
+
let childIndent;
|
|
53
|
+
for (let i = refIdx + 1; i < snapLines.length; i++) {
|
|
54
|
+
const lineIndent = indentOf(snapLines[i]);
|
|
55
|
+
if (lineIndent <= indent)
|
|
56
|
+
break;
|
|
57
|
+
childIndent ??= lineIndent;
|
|
58
|
+
if (lineIndent !== childIndent)
|
|
59
|
+
continue;
|
|
60
|
+
const child = snapLines[i].match(/^\s*-\s*text:\s*(.+?)\s*$/)?.[1];
|
|
61
|
+
if (child !== void 0)
|
|
62
|
+
return unquoteYaml(child);
|
|
63
|
+
}
|
|
64
|
+
return "";
|
|
65
|
+
}
|
|
66
|
+
function snapshotLineText(line) {
|
|
67
|
+
const quoted = line.match(/^\s*-\s*\w+\s+"([^"]*)"/);
|
|
68
|
+
if (quoted)
|
|
69
|
+
return quoted[1];
|
|
70
|
+
const colon = line.match(/\[ref=\w+\](?:\s*\[[^\]]*\])*\s*:\s*(.+?)\s*$/);
|
|
71
|
+
const value = colon?.[1]?.trim();
|
|
72
|
+
return value ? unquoteYaml(value) : null;
|
|
73
|
+
}
|
|
74
|
+
function unquoteYaml(value) {
|
|
75
|
+
if (/^"(?:[^"\\]|\\.)*"$/.test(value)) {
|
|
76
|
+
try {
|
|
77
|
+
return JSON.parse(value);
|
|
78
|
+
} catch {
|
|
79
|
+
return value;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return value;
|
|
83
|
+
}
|
|
84
|
+
function snapshotMatches(snapLines, locator) {
|
|
85
|
+
if (locator.next)
|
|
86
|
+
return void 0;
|
|
87
|
+
const text = locator.kind === "text" ? locator.body : locator.kind === "role" ? locator.options?.name : void 0;
|
|
88
|
+
if (typeof text !== "string")
|
|
89
|
+
return void 0;
|
|
90
|
+
const exact = !!locator.options?.exact;
|
|
91
|
+
const same = (v) => v !== null && (exact ? v === text : v.toLowerCase().includes(text.toLowerCase()));
|
|
92
|
+
return snapLines.filter((l) => {
|
|
93
|
+
if (locator.kind === "role") {
|
|
94
|
+
const m = l.match(/^\s*-\s*(\w+)\s+"([^"]*)"/);
|
|
95
|
+
return !!m && m[1] === locator.body && same(m[2]);
|
|
96
|
+
}
|
|
97
|
+
return same(snapshotLineText(l));
|
|
98
|
+
}).length;
|
|
99
|
+
}
|
|
100
|
+
function isVolatileCss(selector) {
|
|
101
|
+
return selector.startsWith("#") && (0, import_dynamicId.isDynamicId)(selector.slice(1));
|
|
102
|
+
}
|
|
103
|
+
function isTestHookCss(selector) {
|
|
104
|
+
return /^(?:[a-z][\w-]*)?\[data-[\w-]+=(?:"[^"]+"|'[^']+')\]$/i.test(selector);
|
|
105
|
+
}
|
|
106
|
+
function renderSelector(locator) {
|
|
107
|
+
const q = (v, exact) => `${JSON.stringify(v)}${exact ? "s" : "i"}`;
|
|
108
|
+
let head;
|
|
109
|
+
switch (locator.kind) {
|
|
110
|
+
case "test-id":
|
|
111
|
+
head = `internal:testid=[${locator.options?.attribute ?? "data-testid"}=${JSON.stringify(locator.body)}s]`;
|
|
112
|
+
break;
|
|
113
|
+
case "role":
|
|
114
|
+
head = `internal:role=${locator.body}${typeof locator.options?.name === "string" ? `[name=${q(locator.options.name, locator.options.exact)}]` : ""}`;
|
|
115
|
+
break;
|
|
116
|
+
case "text":
|
|
117
|
+
head = `internal:text=${q(locator.body, locator.options?.exact)}`;
|
|
118
|
+
break;
|
|
119
|
+
case "label":
|
|
120
|
+
head = `internal:label=${q(locator.body, locator.options?.exact)}`;
|
|
121
|
+
break;
|
|
122
|
+
case "has-text":
|
|
123
|
+
head = `internal:has-text=${q(locator.body, locator.options?.exact)}`;
|
|
124
|
+
break;
|
|
125
|
+
case "placeholder":
|
|
126
|
+
case "alt":
|
|
127
|
+
case "title":
|
|
128
|
+
head = `internal:attr=[${locator.kind}=${q(locator.body, locator.options?.exact)}]`;
|
|
129
|
+
break;
|
|
130
|
+
case "nth":
|
|
131
|
+
head = `nth=${locator.body}`;
|
|
132
|
+
break;
|
|
133
|
+
default:
|
|
134
|
+
head = String(locator.body);
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
return locator.next ? `${head} >> ${renderSelector(locator.next)}` : head;
|
|
138
|
+
}
|
|
139
|
+
function leaf(locator) {
|
|
140
|
+
let tail = locator;
|
|
141
|
+
while (tail.next)
|
|
142
|
+
tail = tail.next;
|
|
143
|
+
return tail;
|
|
144
|
+
}
|
|
145
|
+
const copyChain = (l) => ({ ...l, options: { ...l.options }, ...l.next ? { next: copyChain(l.next) } : {} });
|
|
146
|
+
function exactLeaf(parsed, fullText) {
|
|
147
|
+
const locator = copyChain(parsed.locator);
|
|
148
|
+
const tail = leaf(locator);
|
|
149
|
+
const leafText = tail.kind === "text" ? tail.body : tail.kind === "role" ? tail.options?.name : void 0;
|
|
150
|
+
if (typeof leafText !== "string" || leafText !== fullText)
|
|
151
|
+
return parsed;
|
|
152
|
+
tail.options = { ...tail.options, exact: true };
|
|
153
|
+
return { selector: renderSelector(locator), locator };
|
|
154
|
+
}
|
|
155
|
+
function withNth(parsed, index) {
|
|
156
|
+
const locator = copyChain(parsed.locator);
|
|
157
|
+
leaf(locator).next = { kind: "nth", body: String(index), options: { attrs: [], exact: false } };
|
|
158
|
+
return { selector: renderSelector(locator), locator };
|
|
159
|
+
}
|
|
160
|
+
function selectorParts(selector) {
|
|
161
|
+
const parts = [];
|
|
162
|
+
let quote;
|
|
163
|
+
let depth = 0;
|
|
164
|
+
let start = 0;
|
|
165
|
+
for (let i = 0; i < selector.length; i++) {
|
|
166
|
+
const c = selector[i];
|
|
167
|
+
if (quote) {
|
|
168
|
+
if (c === "\\")
|
|
169
|
+
i++;
|
|
170
|
+
else if (c === quote)
|
|
171
|
+
quote = void 0;
|
|
172
|
+
} else if (c === '"' || c === "'") {
|
|
173
|
+
quote = c;
|
|
174
|
+
} else if (c === "[" || c === "(") {
|
|
175
|
+
depth++;
|
|
176
|
+
} else if (c === "]" || c === ")") {
|
|
177
|
+
depth--;
|
|
178
|
+
} else if (!depth && selector.startsWith(" >> ", i)) {
|
|
179
|
+
parts.push(selector.slice(start, i));
|
|
180
|
+
start = i + 4;
|
|
181
|
+
i += 3;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
parts.push(selector.slice(start));
|
|
185
|
+
return parts;
|
|
186
|
+
}
|
|
187
|
+
const QUOTED = '("(?:[^"\\\\]|\\\\.)*")([is])';
|
|
188
|
+
function decodeQuoted(quoted) {
|
|
189
|
+
try {
|
|
190
|
+
return JSON.parse(quoted.replace(/[\u0000-\u001f]/g, (c) => "\\u" + c.charCodeAt(0).toString(16).padStart(4, "0")));
|
|
191
|
+
} catch {
|
|
192
|
+
return void 0;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
function locatorFromSelector(selector, opts = {}) {
|
|
196
|
+
const links = [];
|
|
197
|
+
for (const part of selectorParts(selector)) {
|
|
198
|
+
let m;
|
|
199
|
+
let value;
|
|
200
|
+
if (m = part.match(/^nth=(-?\d+)$/)) {
|
|
201
|
+
links.push({ kind: "nth", body: m[1], options: { attrs: [], exact: false } });
|
|
202
|
+
} else if (m = part.match(new RegExp(`^internal:role=([a-z]+)(?:\\[name=${QUOTED}\\])?$`))) {
|
|
203
|
+
if (m[2] && (value = decodeQuoted(m[2])) === void 0)
|
|
204
|
+
return null;
|
|
205
|
+
links.push({ kind: "role", body: m[1], options: { attrs: [], exact: m[3] === "s", ...m[2] ? { name: value } : {} } });
|
|
206
|
+
} else if (m = part.match(new RegExp(`^internal:testid=\\[([\\w-]+)=${QUOTED}\\]$`))) {
|
|
207
|
+
if ((value = decodeQuoted(m[2])) === void 0)
|
|
208
|
+
return null;
|
|
209
|
+
links.push({ kind: "test-id", body: value, options: m[1] === "data-testid" ? {} : { attribute: m[1] } });
|
|
210
|
+
} else if (m = part.match(new RegExp(`^internal:(label|text|has-text)=${QUOTED}$`))) {
|
|
211
|
+
if ((value = decodeQuoted(m[2])) === void 0)
|
|
212
|
+
return null;
|
|
213
|
+
links.push({ kind: m[1], body: value, options: { exact: m[3] === "s" } });
|
|
214
|
+
} else if (m = part.match(new RegExp(`^internal:attr=\\[(placeholder|alt|title)=${QUOTED}\\]$`))) {
|
|
215
|
+
if ((value = decodeQuoted(m[2])) === void 0)
|
|
216
|
+
return null;
|
|
217
|
+
links.push({ kind: m[1], body: value, options: { exact: m[3] === "s" } });
|
|
218
|
+
} else if (/^(?:[a-z-]+=|internal:|\/\/|\.\.)/i.test(part) || !part.trim()) {
|
|
219
|
+
return null;
|
|
220
|
+
} else {
|
|
221
|
+
links.push({ kind: "default", body: part, options: {} });
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
while (!opts.keepTrailingNth && links.length && links[links.length - 1].kind === "nth")
|
|
225
|
+
links.pop();
|
|
226
|
+
if (!links.length || links[0].kind === "nth")
|
|
227
|
+
return null;
|
|
228
|
+
for (let i = links.length - 1; i > 0; i--)
|
|
229
|
+
links[i - 1].next = links[i];
|
|
230
|
+
return links[0];
|
|
231
|
+
}
|
|
232
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
233
|
+
0 && (module.exports = {
|
|
234
|
+
exactLeaf,
|
|
235
|
+
isInputLine,
|
|
236
|
+
isTestHookCss,
|
|
237
|
+
isVolatileCss,
|
|
238
|
+
leaf,
|
|
239
|
+
locatorFromSelector,
|
|
240
|
+
renderSelector,
|
|
241
|
+
snapshotInputValue,
|
|
242
|
+
snapshotLineText,
|
|
243
|
+
snapshotMatches,
|
|
244
|
+
unquoteYaml,
|
|
245
|
+
withNth
|
|
246
|
+
});
|