@sdods/core 0.3.2 → 0.5.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.
Files changed (56) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/api/client.d.ts +62 -1
  3. package/dist/api/client.js +189 -27
  4. package/dist/api/template.d.ts +15 -2
  5. package/dist/api/template.js +31 -13
  6. package/dist/auth/capture.js +4 -3
  7. package/dist/auth/index.js +3 -18
  8. package/dist/config/defaults.js +2 -0
  9. package/dist/config/resolve.d.ts +5 -1
  10. package/dist/config/resolve.js +16 -3
  11. package/dist/config/runner.d.ts +26 -1
  12. package/dist/config/runner.js +155 -24
  13. package/dist/config/tags.d.ts +14 -2
  14. package/dist/config/tags.js +27 -7
  15. package/dist/evidence/index.d.ts +3 -0
  16. package/dist/evidence/index.js +3 -0
  17. package/dist/evidence/trace-redact.d.ts +74 -0
  18. package/dist/evidence/trace-redact.js +297 -0
  19. package/dist/evidence/zip.d.ts +36 -0
  20. package/dist/evidence/zip.js +148 -0
  21. package/dist/fixtures/api-context.d.ts +7 -0
  22. package/dist/fixtures/api-context.js +7 -0
  23. package/dist/fixtures/auth.js +72 -5
  24. package/dist/fixtures/test.js +9 -2
  25. package/dist/heal/healer.d.ts +12 -1
  26. package/dist/heal/healer.js +80 -34
  27. package/dist/heal/strategies.d.ts +12 -2
  28. package/dist/heal/strategies.js +42 -0
  29. package/dist/index.d.ts +1 -0
  30. package/dist/index.js +1 -0
  31. package/dist/lint/index.d.ts +1 -0
  32. package/dist/lint/index.js +44 -3
  33. package/dist/lint/steps.d.ts +13 -0
  34. package/dist/lint/steps.js +333 -0
  35. package/dist/load-module.d.ts +7 -0
  36. package/dist/load-module.js +70 -0
  37. package/dist/logger.d.ts +1 -0
  38. package/dist/logger.js +9 -3
  39. package/dist/steps/a11y.steps.js +10 -10
  40. package/dist/steps/api.steps.js +64 -27
  41. package/dist/steps/browser.steps.js +34 -33
  42. package/dist/steps/clock.steps.js +5 -5
  43. package/dist/steps/data.steps.js +61 -8
  44. package/dist/steps/db.steps.js +8 -8
  45. package/dist/steps/dom.steps.js +49 -49
  46. package/dist/steps/hybrid.steps.js +9 -5
  47. package/dist/steps/iframe.steps.js +9 -9
  48. package/dist/steps/net.steps.d.ts +12 -7
  49. package/dist/steps/net.steps.js +78 -83
  50. package/dist/steps/perf.steps.js +6 -6
  51. package/dist/steps/tabs.steps.js +6 -6
  52. package/dist/steps/ui.steps.js +94 -45
  53. package/dist/steps/webhook.steps.js +3 -3
  54. package/dist/version.d.ts +1 -1
  55. package/dist/version.js +15 -1
  56. package/package.json +6 -4
@@ -2,16 +2,51 @@ import { existsSync } from 'node:fs';
2
2
  import { join, resolve as resolvePath } from 'node:path';
3
3
  import { devices } from '@playwright/test';
4
4
  import { cucumberReporter, defineBddConfig } from 'playwright-bdd';
5
- import { runnerProjectName, runFiles } from '@sdods/contracts';
5
+ import { EVIDENCE_DEFAULTS, RUNNER_SETUP_PHASE, runnerProjectName, runFiles, } from '@sdods/contracts';
6
6
  import { coreStepsPatterns } from '../steps/glob.js';
7
7
  import { combineTagExpr, normalizeTagExpr } from './tags.js';
8
8
  const DEVICE_FOR_BROWSER = {
9
9
  chromium: 'Desktop Chrome',
10
+ edge: 'Desktop Edge',
10
11
  firefox: 'Desktop Firefox',
11
12
  webkit: 'Desktop Safari',
12
13
  'mobile-chrome': 'Pixel 7',
13
14
  'mobile-safari': 'iPhone 15',
14
15
  };
16
+ /**
17
+ * Browsers that are a branded channel of an engine rather than the bundled build.
18
+ *
19
+ * `devices['Desktop Edge']` only sets an Edge user-agent — its `defaultBrowserType` is still
20
+ * 'chromium' — so without an explicit channel Playwright launches the bundled Chromium and it
21
+ * merely claims to be Edge. The channel is what actually starts the browser the user installed.
22
+ */
23
+ const CHANNEL_FOR_BROWSER = { edge: 'msedge' };
24
+ /** The Playwright channel family a project-level `channel:` belongs to, for compatibility checks. */
25
+ function channelFamily(channel) {
26
+ return channel.startsWith('msedge') ? 'msedge' : 'chrome';
27
+ }
28
+ /**
29
+ * The channel a run target launches with.
30
+ *
31
+ * A project-level `channel:` refines a branded browser, it does not redirect it: `channel: chrome`
32
+ * on a project that also runs `edge` must not make the `--edge` target launch Google Chrome, which
33
+ * would put a browser in the results under another browser's name.
34
+ */
35
+ function channelFor(browser, projectChannel) {
36
+ const branded = CHANNEL_FOR_BROWSER[browser];
37
+ if (!branded) {
38
+ // Chromium-family engines take whatever the project asked for; other engines have no channels.
39
+ return browser === 'chromium' || browser === 'mobile-chrome' ? projectChannel : undefined;
40
+ }
41
+ if (projectChannel && channelFamily(projectChannel) === channelFamily(branded))
42
+ return projectChannel;
43
+ return branded;
44
+ }
45
+ /** The `use` keys that select the browser binary for a run target. */
46
+ function browserUse(browser, projectChannel) {
47
+ const channel = channelFor(browser, projectChannel);
48
+ return { ...devices[DEVICE_FOR_BROWSER[browser]], ...(channel ? { channel } : {}) };
49
+ }
15
50
  export const DASHBOARD_REPORTER = '@sdods/core/reporters/dashboard';
16
51
  /** Convenience for `sdods.runner.config.ts`: read the selection from SDODS_* env vars. */
17
52
  export function selectionFromEnv(env = process.env) {
@@ -32,6 +67,7 @@ export function selectionFromEnv(env = process.env) {
32
67
  allure: env.SDODS_ALLURE === '1',
33
68
  reporters: list(env.SDODS_REPORTERS),
34
69
  reporterMode: env.SDODS_REPORTER_MODE || 'default',
70
+ process: env.SDODS_PROCESS || undefined,
35
71
  };
36
72
  }
37
73
  /** Names (and identity) of the Playwright projects a selection would produce, without side effects. */
@@ -93,32 +129,40 @@ export function buildRunnerConfig(registry, sel = {}) {
93
129
  for (const k of Object.keys(envUse))
94
130
  if (envUse[k] === undefined)
95
131
  delete envUse[k];
132
+ const byTag = p.retries?.byTag ?? {};
133
+ const pushProject = (project) => projects.push(...withRetriesByTag(project, byTag));
134
+ const { fullyParallel, setup } = runSettings(registry, entry.slug, p, sel.process);
135
+ // Playwright's own artifacts, per target so each project in a multi-project run keeps its own.
136
+ const evidenceUse = {
137
+ screenshot: p.evidence.screenshot,
138
+ video: p.evidence.video,
139
+ trace: p.evidence.trace,
140
+ };
96
141
  for (const layer of layers) {
97
142
  if (layer === 'recorded') {
98
143
  const recordedDir = join(p.root, 'recorded');
99
144
  if (!existsSync(recordedDir))
100
145
  continue;
101
146
  for (const browser of browsers) {
102
- projects.push({
147
+ pushProject({
103
148
  name: runnerProjectName({ project: p.slug, layer, browser }),
104
149
  testDir: recordedDir,
105
150
  testMatch: '**/*.spec.ts',
151
+ fullyParallel,
106
152
  snapshotPathTemplate: join(p.root, 'features', '__screenshots__', '{projectName}', '{platform}', '{arg}{ext}'),
107
153
  use: {
108
- ...devices[DEVICE_FOR_BROWSER[browser]],
109
- ...(p.channel && (browser === 'chromium' || browser === 'mobile-chrome')
110
- ? { channel: p.channel }
111
- : {}),
154
+ ...browserUse(browser, p.channel),
112
155
  baseURL: cfg.env.ui.baseUrl,
113
156
  testIdAttribute: p.testIdAttribute,
114
157
  ...envUse,
158
+ ...evidenceUse,
115
159
  sdods: { project: p.slug, layer, browser },
116
160
  },
117
161
  });
118
162
  }
119
163
  continue;
120
164
  }
121
- const testDir = defineBddConfig({
165
+ const generate = (tags, outDir) => defineBddConfig({
122
166
  features: `${toPosix(p.root)}/features/**/*.feature`,
123
167
  steps: [
124
168
  ...coreStepsPatterns(cfg.project.steps?.core?.exclude ?? []),
@@ -127,42 +171,66 @@ export function buildRunnerConfig(registry, sel = {}) {
127
171
  ],
128
172
  // Each run generates into its own dir (cleaned by `sdods run`), lint/export into `.lint`,
129
173
  // so concurrent runs and tooling never race on generated specs.
130
- outputDir: `${toPosix(join(cfg.runtime.repoRoot, '.sdods/generated', sel.lint ? '.lint' : (sel.runId ?? 'adhoc'), p.slug, layer))}`,
174
+ outputDir: `${toPosix(join(cfg.runtime.repoRoot, '.sdods/generated', sel.lint ? '.lint' : (sel.runId ?? 'adhoc'), p.slug, outDir))}`,
131
175
  featuresRoot: `${toPosix(p.root)}/features`,
132
176
  // Explicit: scenarios that only use core steps cannot let bddgen guess the project test instance.
133
177
  importTestFrom: `${toPosix(p.root)}/steps/fixtures.ts`,
134
178
  disableWarnings: { importTestFrom: true },
135
- tags: combineTagExpr(`@${layer}`, sel.tags),
179
+ tags,
136
180
  examplesTitleFormat: 'Example #<_index_>',
137
181
  missingSteps: sel.lint ? 'fail-on-gen' : 'fail-on-run',
138
182
  aiFix: { promptAttachment: true },
139
183
  quotes: 'single',
140
184
  });
185
+ const layerExpr = combineTagExpr(`@${layer}`, sel.tags);
186
+ // Setup scenarios are generated on their own: whatever --tags selects, the gate still runs,
187
+ // and a scenario that is setup never runs a second time inside the target it gates.
188
+ const testDir = generate(setup ? `(${layerExpr}) and not (${setup.tags})` : layerExpr, layer);
141
189
  bddConfigs++;
190
+ const setupTestDir = setup
191
+ ? generate(combineTagExpr(`@${layer}`, setup.tags), `${layer}--${RUNNER_SETUP_PHASE}`)
192
+ : undefined;
193
+ if (setupTestDir)
194
+ bddConfigs++;
195
+ /** Push a target, preceded by its setup companion when the project has a setup tier. */
196
+ const pushTarget = (parts, target) => {
197
+ const name = runnerProjectName({ project: p.slug, ...parts });
198
+ if (setupTestDir) {
199
+ const setupName = runnerProjectName({
200
+ project: p.slug,
201
+ ...parts,
202
+ phase: RUNNER_SETUP_PHASE,
203
+ });
204
+ projects.push({ ...target, name: setupName, testDir: setupTestDir });
205
+ pushProject({ ...target, name, testDir, dependencies: [setupName] });
206
+ }
207
+ else {
208
+ pushProject({ ...target, name, testDir });
209
+ }
210
+ };
142
211
  if (layer === 'api') {
143
- projects.push({
144
- name: runnerProjectName({ project: p.slug, layer }),
145
- testDir,
146
- use: { sdods: { project: p.slug, layer } },
212
+ pushTarget({ layer }, {
213
+ fullyParallel,
214
+ use: {
215
+ ...evidenceUse,
216
+ sdods: { project: p.slug, layer },
217
+ },
147
218
  });
148
219
  continue;
149
220
  }
150
221
  for (const browser of browsers) {
151
- projects.push({
152
- name: runnerProjectName({ project: p.slug, layer, browser }),
153
- testDir,
222
+ pushTarget({ layer, browser }, {
223
+ fullyParallel,
154
224
  // Baselines live with the project (generated specs are per-run and deleted):
155
225
  // projects/<slug>/features/__screenshots__/<pw project>/<platform>/<name>.png
156
226
  snapshotPathTemplate: join(p.root, 'features', '__screenshots__', '{projectName}', '{platform}', '{arg}{ext}'),
157
227
  use: {
158
- ...devices[DEVICE_FOR_BROWSER[browser]],
159
- ...(p.channel && (browser === 'chromium' || browser === 'mobile-chrome')
160
- ? { channel: p.channel }
161
- : {}),
228
+ ...browserUse(browser, p.channel),
162
229
  baseURL: cfg.env.ui.baseUrl,
163
230
  testIdAttribute: p.testIdAttribute,
164
231
  viewport: browser.startsWith('mobile') ? undefined : p.screenshots.viewport,
165
232
  ...envUse,
233
+ ...evidenceUse,
166
234
  sdods: { project: p.slug, layer, browser },
167
235
  },
168
236
  });
@@ -227,13 +295,16 @@ export function buildRunnerConfig(registry, sel = {}) {
227
295
  expect: { timeout: timeouts.expect },
228
296
  retries,
229
297
  workers,
230
- fullyParallel: true,
298
+ // Each generated project sets its own; this is the fallback for anything added by hand.
299
+ fullyParallel: first
300
+ ? runSettings(registry, first.project.slug, first.project, sel.process).fullyParallel
301
+ : true,
231
302
  outputDir: join(runDir, runFiles.output),
232
303
  reporter,
233
304
  use: {
234
- screenshot: 'off',
235
- video: 'retain-on-failure',
236
- trace: 'on-first-retry',
305
+ screenshot: first?.project.evidence.screenshot ?? EVIDENCE_DEFAULTS.screenshot,
306
+ video: first?.project.evidence.video ?? EVIDENCE_DEFAULTS.video,
307
+ trace: first?.project.evidence.trace ?? EVIDENCE_DEFAULTS.trace,
237
308
  actionTimeout: timeouts.action,
238
309
  navigationTimeout: timeouts.navigation,
239
310
  // Playwright's own `--headed` flag still wins on the CLI; this is what makes the documented
@@ -248,6 +319,66 @@ export function buildRunnerConfig(registry, sel = {}) {
248
319
  },
249
320
  };
250
321
  }
322
+ /**
323
+ * `retries.byTag` as runner projects.
324
+ *
325
+ * Retries are fixed when a test is collected: Playwright takes them from the enclosing
326
+ * `test.describe.configure` or the project, and offers no runtime setter; bddgen has no hook to
327
+ * add `@retries:N` to a scenario it did not read from the feature file. What Playwright does
328
+ * offer is project-level `grep`, matched against the title AND the tags. So each byTag entry
329
+ * becomes a sibling of the project — same name, so `--project <name>`, reports and dashboards are
330
+ * unchanged — that greps for the tag and carries its retries, and the base project inverts every
331
+ * byTag grep. Entries are ordered by retries, highest first, and each sibling also inverts the
332
+ * ones before it, so a scenario with two such tags runs exactly once, with the larger count.
333
+ *
334
+ * Explicit `--retries` on the CLI still overrides every project, these included. A scenario's own
335
+ * `@retries:N` tag (playwright-bdd) wins over all of it.
336
+ */
337
+ export function withRetriesByTag(project, byTag) {
338
+ const entries = Object.entries(byTag)
339
+ .map(([tag, retries]) => ({ tag: tag.startsWith('@') ? tag : `@${tag}`, retries }))
340
+ .sort((a, b) => b.retries - a.retries);
341
+ if (!entries.length)
342
+ return [project];
343
+ // Whitespace-delimited: Playwright joins the title path and tags with spaces, so `@flaky`
344
+ // must not match `@flakyish`. (A scenario TITLE containing the literal tag also matches.)
345
+ const tagRe = (tag) => new RegExp(`(^|\\s)${tag.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(\\s|$)`);
346
+ const seen = [];
347
+ const out = [];
348
+ for (const { tag, retries } of entries) {
349
+ const re = tagRe(tag);
350
+ out.push({
351
+ ...project,
352
+ grep: re,
353
+ ...(seen.length ? { grepInvert: [...seen] } : {}),
354
+ retries,
355
+ });
356
+ seen.push(re);
357
+ }
358
+ return [{ ...project, grepInvert: seen }, ...out];
359
+ }
360
+ /**
361
+ * `fullyParallel` and the setup tier for one project, with a named process taking precedence.
362
+ * A process that is not defined for this project (a multi-project run) leaves the project's own.
363
+ */
364
+ function runSettings(registry, slug, project, processName) {
365
+ const proc = processName
366
+ ? registry.processesOf(slug).find((x) => x.name === processName)
367
+ : undefined;
368
+ return {
369
+ fullyParallel: proc?.fullyParallel ?? project.fullyParallel,
370
+ setup: setupTierOf(project, proc),
371
+ };
372
+ }
373
+ /**
374
+ * The setup tier a run uses: the process's `setup` (`false` turns it off), else the project's.
375
+ * Tags are normalised like `--tags`, so `setup: { tags: setup }` means `@setup`.
376
+ */
377
+ export function setupTierOf(project, proc) {
378
+ const setup = proc?.setup === false ? undefined : (proc?.setup ?? project.setup);
379
+ const tags = normalizeTagExpr(setup?.tags);
380
+ return tags ? { tags } : undefined;
381
+ }
251
382
  function parseEnvOverrides() {
252
383
  const raw = process.env.SDODS_CLI_OVERRIDES;
253
384
  if (!raw)
@@ -1,4 +1,4 @@
1
- import type { ProjectConfig } from '@sdods/contracts';
1
+ import { type ProjectConfig } from '@sdods/contracts';
2
2
  export declare const LAYER_TAGS: readonly ["@ui", "@api", "@hybrid"];
3
3
  /** Control tags the runner interprets itself; lint passes them through untouched. */
4
4
  export declare const RUNNER_SPECIAL_TAGS: RegExp;
@@ -6,7 +6,11 @@ export declare const RUNNER_SPECIAL_TAGS: RegExp;
6
6
  export declare const PLAYWRIGHT_BDD_SPECIAL: RegExp;
7
7
  export declare const VALUE_TAG: RegExp;
8
8
  export declare const KNOWN_VALUE_TAGS: readonly ["env", "user", "data", "har", "jira", "github", "skip", "title", "flag"];
9
- export declare const BROWSERS_FOR_SKIP: readonly ["chromium", "firefox", "webkit", "mobile-chrome", "mobile-safari"];
9
+ /**
10
+ * Values `@skip:<browser>` accepts. Derived from the schema rather than copied, so adding a browser
11
+ * to `BrowserSchema` is the only edit needed — and the lint message lists the new name for free.
12
+ */
13
+ export declare const BROWSERS_FOR_SKIP: ("chromium" | "edge" | "firefox" | "webkit" | "mobile-chrome" | "mobile-safari")[];
10
14
  export interface TagTaxonomy {
11
15
  layers: readonly string[];
12
16
  suites: string[];
@@ -24,6 +28,14 @@ export declare function suiteOfTags(tags: readonly string[], suites: readonly st
24
28
  export declare function combineTagExpr(layerTag: string, userExpr?: string): string;
25
29
  /** `--tags @smoke` shorthand also accepts comma lists ("@smoke,@sanity" → "@smoke or @sanity"). */
26
30
  export declare function normalizeTagExpr(input?: string): string | undefined;
31
+ export interface TagExpr {
32
+ evaluate(tags: readonly string[]): boolean;
33
+ }
34
+ /**
35
+ * Parse a Cucumber tag expression, or fail as a configuration error (exit 2) that quotes it.
36
+ * Every command that selects scenarios by tag goes through here so they agree on what matches.
37
+ */
38
+ export declare function parseTagExpr(expr: string): TagExpr;
27
39
  export interface TagGateContext {
28
40
  /** `config.env.name` — the environment this run is actually pointed at. */
29
41
  env: string;
@@ -1,3 +1,6 @@
1
+ import parseInfix from 'cucumber-tag-expressions';
2
+ import { BrowserSchema } from '@sdods/contracts';
3
+ import { SdodsError } from '../errors.js';
1
4
  export const LAYER_TAGS = ['@ui', '@api', '@hybrid'];
2
5
  /** Control tags the runner interprets itself; lint passes them through untouched. */
3
6
  export const RUNNER_SPECIAL_TAGS = /^@(only|skip|fixme|fail|slow|timeout:\d+|retries:\d+|mode:(parallel|serial|default))$/;
@@ -15,13 +18,11 @@ export const KNOWN_VALUE_TAGS = [
15
18
  'title',
16
19
  'flag',
17
20
  ];
18
- export const BROWSERS_FOR_SKIP = [
19
- 'chromium',
20
- 'firefox',
21
- 'webkit',
22
- 'mobile-chrome',
23
- 'mobile-safari',
24
- ];
21
+ /**
22
+ * Values `@skip:<browser>` accepts. Derived from the schema rather than copied, so adding a browser
23
+ * to `BrowserSchema` is the only edit needed — and the lint message lists the new name for free.
24
+ */
25
+ export const BROWSERS_FOR_SKIP = BrowserSchema.options;
25
26
  export function taxonomyFromProject(project) {
26
27
  return {
27
28
  layers: LAYER_TAGS,
@@ -75,6 +76,25 @@ export function normalizeTagExpr(input) {
75
76
  .map((t) => (t.startsWith('@') ? t : `@${t}`));
76
77
  return parts.length > 1 ? parts.join(' or ') : parts[0];
77
78
  }
79
+ // The package is CommonJS with `exports.default`; ESM interop may hand back the module object.
80
+ const parseInfixFn = (parseInfix.default ??
81
+ parseInfix);
82
+ /**
83
+ * Parse a Cucumber tag expression, or fail as a configuration error (exit 2) that quotes it.
84
+ * Every command that selects scenarios by tag goes through here so they agree on what matches.
85
+ */
86
+ export function parseTagExpr(expr) {
87
+ try {
88
+ const node = parseInfixFn(expr);
89
+ return { evaluate: (tags) => node.evaluate([...tags]) };
90
+ }
91
+ catch (e) {
92
+ throw new SdodsError('CONFIG_INVALID', `Invalid tag expression "${expr}": ${e.message}`, {
93
+ hint: 'Combine tags with and / or / not and balanced parentheses, e.g. "@ui and (@smoke or @sanity)".',
94
+ exitCode: 2,
95
+ });
96
+ }
97
+ }
78
98
  /**
79
99
  * Why this scenario should not run here, or undefined to run it.
80
100
  *
@@ -0,0 +1,3 @@
1
+ export * from './trace-redact.js';
2
+ export { readZip, writeZip, entryContent, isZip, ZipFormatError, type ZipEntry } from './zip.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,3 @@
1
+ export * from './trace-redact.js';
2
+ export { readZip, writeZip, entryContent, isZip, ZipFormatError } from './zip.js';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,74 @@
1
+ import { type ZipEntry } from './zip.js';
2
+ /**
3
+ * Strip credentials out of Playwright traces (#101).
4
+ *
5
+ * A trace records every request and response header, the context's options (`extraHTTPHeaders`,
6
+ * `httpCredentials`, `storageState`), every `storageState()` / `addCookies()` call with its result,
7
+ * DOM snapshots and the arguments of every action. For a project that signs in with pool accounts
8
+ * that is the session cookie, the `Authorization` bearer and — for Firebase — the long-lived
9
+ * refresh token in localStorage/IndexedDB.
10
+ *
11
+ * What is replaced with `[redacted]`:
12
+ * - credential headers (`Cookie`, `Set-Cookie`, `Authorization`, `Proxy-Authorization`,
13
+ * `X-Api-Key` and similar) wherever a `{ name, value }` header or a header map appears —
14
+ * `.network` request/response entries, context options, route and fetch parameters;
15
+ * - every cookie value, localStorage/sessionStorage value and IndexedDB record;
16
+ * - `password` / `passphrase` fields (`httpCredentials`, `proxy`, client certificates);
17
+ * - the value of password inputs in DOM snapshots, and values typed by `fill`/`type` into a field
18
+ * whose locator names a password, secret, token or one-time code (best effort).
19
+ *
20
+ * Not covered: response and request bodies (`resources/`), page text and screenshots. A redacted
21
+ * trace is safer to keep, not safe to publish.
22
+ *
23
+ * Only `*.trace` and `*.network` entries are rewritten; everything else (sources, screenshots,
24
+ * resources, stacks) is copied through unchanged, so the trace viewer opens the result as before.
25
+ */
26
+ export declare const REDACTED = "[redacted]";
27
+ export declare const TRACE_SECRET_HEADER: RegExp;
28
+ /**
29
+ * Redact one parsed trace event in place. Returns the number of values replaced.
30
+ *
31
+ * `typed` carries values redacted from typing actions across the events of one stream, by call id,
32
+ * so the action's `log` lines (`fill("…")`) lose the value too.
33
+ */
34
+ export declare function redactTraceEvent(event: unknown, typed?: Map<string, string>): number;
35
+ /** Redact every JSON line of a `.trace` / `.network` entry. Unparseable lines are kept as they are. */
36
+ export declare function redactTraceJsonl(text: string): {
37
+ text: string;
38
+ changed: number;
39
+ };
40
+ /** True when the buffer is a zip holding a Playwright trace (or a zip that nests one). */
41
+ export declare function isTraceZip(entries: ZipEntry[]): boolean;
42
+ /**
43
+ * Redact a trace zip held in memory. Zips nested inside (blob shard reports keep attachments as
44
+ * `resources/<sha>.zip`) are redacted too. Non-trace zips come back untouched with `trace: false`.
45
+ */
46
+ export declare function redactTraceZip(buf: Buffer): {
47
+ buffer: Buffer;
48
+ changed: number;
49
+ trace: boolean;
50
+ };
51
+ /** Redact a trace zip on disk in place. Returns values replaced (0 when not a trace). */
52
+ export declare function redactTraceFile(file: string): number;
53
+ /**
54
+ * Cucumber messages embed each trace as a BASE64 attachment body, and ingest writes `trace.zip`
55
+ * from those bytes — so the copy in `messages*.ndjson` is redacted as well.
56
+ */
57
+ export declare function redactMessagesFile(file: string): number;
58
+ export interface RunTraceRedaction {
59
+ /** trace zips and message files that were rewritten */
60
+ files: string[];
61
+ /** values replaced across all of them */
62
+ values: number;
63
+ /** files that could not be read or rewritten, with the reason */
64
+ errors: Array<{
65
+ file: string;
66
+ error: string;
67
+ }>;
68
+ }
69
+ /**
70
+ * Redact every trace a run left behind: `runner-output/<test>/trace.zip`, the HTML report's copies
71
+ * under `html-report/data/`, blob shard reports, and the BASE64 bodies in `messages*.ndjson`.
72
+ */
73
+ export declare function redactRunTraces(runDir: string, maxDepth?: number): RunTraceRedaction;
74
+ //# sourceMappingURL=trace-redact.d.ts.map