@sdods/core 0.2.2 → 0.3.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/dist/.tsbuildinfo +1 -1
- package/dist/analyze/detectors.js +236 -24
- package/dist/analyze/index.d.ts +0 -1
- package/dist/analyze/index.js +0 -1
- package/dist/analyze/propose.js +80 -38
- package/dist/analyze/scan.js +19 -1
- package/dist/api/client.js +7 -1
- package/dist/auth/capture.js +19 -6
- package/dist/auth/index.js +23 -2
- package/dist/config/resolve.d.ts +13 -0
- package/dist/config/resolve.js +1 -0
- package/dist/config/runner.js +2 -2
- package/dist/config/tags.d.ts +29 -1
- package/dist/config/tags.js +46 -0
- package/dist/data/provider.js +5 -1
- package/dist/data/user-pool.js +35 -3
- package/dist/fixtures/api-context.d.ts +14 -1
- package/dist/fixtures/api-context.js +13 -0
- package/dist/fixtures/scenario.js +1 -4
- package/dist/fixtures/test.js +42 -1
- package/dist/fixtures/types.d.ts +2 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/reporters/dashboard.d.ts +86 -0
- package/dist/reporters/dashboard.js +319 -61
- package/dist/shots/hooks.js +0 -10
- package/dist/steps/a11y.steps.d.ts +180 -0
- package/dist/steps/a11y.steps.js +598 -0
- package/dist/steps/api.steps.js +5 -1
- package/dist/steps/browser.steps.d.ts +27 -0
- package/dist/steps/browser.steps.js +653 -0
- package/dist/steps/clock.steps.d.ts +4 -0
- package/dist/steps/clock.steps.js +73 -0
- package/dist/steps/data.steps.js +50 -2
- package/dist/steps/db.steps.d.ts +5 -0
- package/dist/steps/db.steps.js +105 -0
- package/dist/steps/dom.steps.d.ts +2 -0
- package/dist/steps/dom.steps.js +583 -0
- package/dist/steps/glob.d.ts +16 -1
- package/dist/steps/glob.js +40 -1
- package/dist/steps/iframe.steps.d.ts +2 -0
- package/dist/steps/iframe.steps.js +93 -0
- package/dist/steps/index.d.ts +11 -1
- package/dist/steps/index.js +11 -1
- package/dist/steps/net.steps.d.ts +63 -0
- package/dist/steps/net.steps.js +728 -0
- package/dist/steps/perf.steps.d.ts +248 -0
- package/dist/steps/perf.steps.js +514 -0
- package/dist/steps/tabs.steps.d.ts +5 -0
- package/dist/steps/tabs.steps.js +109 -0
- package/dist/steps/webhook.steps.d.ts +46 -0
- package/dist/steps/webhook.steps.js +129 -0
- package/package.json +3 -4
- package/dist/analyze/modules.d.ts +0 -74
- package/dist/analyze/modules.js +0 -353
- package/dist/config/playwright.d.ts +0 -37
- package/dist/config/playwright.js +0 -262
package/dist/steps/glob.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { readdirSync } from 'node:fs';
|
|
1
2
|
import { dirname } from 'node:path';
|
|
2
3
|
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { SdodsError } from '../errors.js';
|
|
3
5
|
/**
|
|
4
6
|
* Absolute glob of the core step library. Built from this file's real path so Playwright's
|
|
5
7
|
* TS transform sees a workspace path (not node_modules) and playwright-bdd loads it.
|
|
@@ -7,6 +9,13 @@ import { fileURLToPath } from 'node:url';
|
|
|
7
9
|
export function coreStepsDir() {
|
|
8
10
|
return dirname(fileURLToPath(import.meta.url)).replace(/\\/g, '/');
|
|
9
11
|
}
|
|
12
|
+
/** The library names present in this build — `a11y` for `a11y.steps.ts`. */
|
|
13
|
+
export function coreStepNames() {
|
|
14
|
+
return readdirSync(coreStepsDir())
|
|
15
|
+
.map((f) => /^(.+)\.steps\.(?:js|ts)$/.exec(f)?.[1])
|
|
16
|
+
.filter((n) => Boolean(n))
|
|
17
|
+
.sort();
|
|
18
|
+
}
|
|
10
19
|
/**
|
|
11
20
|
* Both extensions, because this resolves against whichever copy of the package is running: the
|
|
12
21
|
* workspace checkout ships `*.steps.ts`, the published package ships the compiled `*.steps.js`.
|
|
@@ -14,8 +23,38 @@ export function coreStepsDir() {
|
|
|
14
23
|
* outside the pattern. playwright-bdd passes explicit patterns to tinyglobby untouched — it only
|
|
15
24
|
* appends extensions to bare directory patterns — and neither it nor tinyglobby excludes
|
|
16
25
|
* node_modules, so the published layout globs fine.
|
|
17
|
-
*/
|
|
26
|
+
* */
|
|
18
27
|
export function coreStepsGlob() {
|
|
19
28
|
return `${coreStepsDir()}/*.steps.{js,ts}`;
|
|
20
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* The patterns bddgen should load, honouring `steps.core.exclude`.
|
|
32
|
+
*
|
|
33
|
+
* Separate from `coreStepsGlob()` because that one is exported API and returns a single string;
|
|
34
|
+
* widening its return type would break every caller for the sake of an option most projects
|
|
35
|
+
* never set. With nothing excluded this returns exactly that same glob, so the common path is
|
|
36
|
+
* byte-identical to what shipped before.
|
|
37
|
+
*
|
|
38
|
+
* An unknown name THROWS rather than being ignored. A silent no-op on a typo would leave the
|
|
39
|
+
* project believing it had excluded a library while the collision it was avoiding still fails
|
|
40
|
+
* generation — and the error it would then read points at the step, not at the typo.
|
|
41
|
+
*/
|
|
42
|
+
export function coreStepsPatterns(exclude = []) {
|
|
43
|
+
if (exclude.length === 0)
|
|
44
|
+
return [coreStepsGlob()];
|
|
45
|
+
const available = coreStepNames();
|
|
46
|
+
const unknown = exclude.filter((n) => !available.includes(n));
|
|
47
|
+
if (unknown.length) {
|
|
48
|
+
throw new SdodsError('CONFIG_INVALID', `steps.core.exclude names ${unknown.length === 1 ? 'a library' : 'libraries'} that ` +
|
|
49
|
+
`${unknown.length === 1 ? 'does' : 'do'} not exist: ${unknown.join(', ')}.`, {
|
|
50
|
+
hint: `Available: ${available.join(', ')}. Names are the file basenames, so "a11y" for a11y.steps.ts.`,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
// Explicit per-library patterns rather than a negated glob: playwright-bdd hands these to
|
|
54
|
+
// tinyglobby verbatim, and a `!`-prefixed entry in that list is treated as a path, not an
|
|
55
|
+
// exclusion.
|
|
56
|
+
return available
|
|
57
|
+
.filter((n) => !exclude.includes(n))
|
|
58
|
+
.map((n) => `${coreStepsDir()}/${n}.steps.{js,ts}`);
|
|
59
|
+
}
|
|
21
60
|
//# sourceMappingURL=glob.js.map
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { expect } from '@playwright/test';
|
|
2
|
+
import './params.js';
|
|
3
|
+
import { Given, Then, When } from '../fixtures/test.js';
|
|
4
|
+
import { render } from '../api/template.js';
|
|
5
|
+
import { SdodsError } from '../errors.js';
|
|
6
|
+
/**
|
|
7
|
+
* Iframe steps.
|
|
8
|
+
*
|
|
9
|
+
* DESIGN — a frame is entered, not passed around. Every other UI step in this
|
|
10
|
+
* library resolves against `page`, and threading an optional frame through all
|
|
11
|
+
* of them would touch every signature for a surface most scenarios never see.
|
|
12
|
+
* So a scenario *enters* a frame, the subsequent steps here operate inside it,
|
|
13
|
+
* and it *leaves*. The scope is per-scenario state, held on the page object
|
|
14
|
+
* rather than in module scope, so parallel workers cannot see each other's.
|
|
15
|
+
*
|
|
16
|
+
* WHY THIS EXISTS — a cross-origin payment frame is the single most common
|
|
17
|
+
* thing a suite cannot reach, and "we do not test checkout" is not a decision
|
|
18
|
+
* anybody made; it is what happens when the vocabulary has no word for it.
|
|
19
|
+
*/
|
|
20
|
+
const scopesOf = (apiContext, env) => [apiContext.vars.toObject(), env.vars];
|
|
21
|
+
/** Per-page frame scope. A WeakMap, so a closed page's entry is collectable. */
|
|
22
|
+
const entered = new WeakMap();
|
|
23
|
+
function currentFrame(page) {
|
|
24
|
+
const scope = entered.get(page);
|
|
25
|
+
if (!scope) {
|
|
26
|
+
throw new SdodsError('NOT_SUPPORTED', 'No iframe has been entered.', {
|
|
27
|
+
hint: 'Use `Given I enter the frame "<selector or name>"` before addressing elements inside it.',
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
return scope.frame;
|
|
31
|
+
}
|
|
32
|
+
/* ── entering and leaving ─────────────────────────────────────────────── */
|
|
33
|
+
Given('I enter the frame {string}', async ({ page, apiContext, env }, selector) => {
|
|
34
|
+
const resolved = render(selector, ...scopesOf(apiContext, env));
|
|
35
|
+
// A bare word is treated as a name/title/id, which is how frames are
|
|
36
|
+
// usually identified in markup; anything else is a CSS selector.
|
|
37
|
+
const css = /^[\w-]+$/.test(resolved)
|
|
38
|
+
? `iframe[name="${resolved}"], iframe[title="${resolved}"], iframe#${resolved}`
|
|
39
|
+
: resolved;
|
|
40
|
+
const handle = page.locator(css).first();
|
|
41
|
+
// Assert the element EXISTS before entering. `frameLocator` on a selector
|
|
42
|
+
// that matches nothing fails later, inside an unrelated step, with a
|
|
43
|
+
// message about the element you were looking for rather than the frame you
|
|
44
|
+
// never entered.
|
|
45
|
+
await expect(handle, `the frame "${resolved}" should be present before entering it`).toBeAttached();
|
|
46
|
+
entered.set(page, { frame: page.frameLocator(css), description: resolved });
|
|
47
|
+
});
|
|
48
|
+
Given('I leave the frame', async ({ page }) => {
|
|
49
|
+
entered.delete(page);
|
|
50
|
+
});
|
|
51
|
+
/* ── acting inside the frame ──────────────────────────────────────────── */
|
|
52
|
+
When('I fill the frame field {string} with {string}', async ({ page, apiContext, env }, label, value) => {
|
|
53
|
+
const scopes = scopesOf(apiContext, env);
|
|
54
|
+
const frame = currentFrame(page);
|
|
55
|
+
await frame
|
|
56
|
+
.getByLabel(render(label, ...scopes))
|
|
57
|
+
.or(frame.getByPlaceholder(render(label, ...scopes)))
|
|
58
|
+
.first()
|
|
59
|
+
.fill(render(value, ...scopes));
|
|
60
|
+
});
|
|
61
|
+
When('I click the frame element {string}', async ({ page, apiContext, env }, selector) => {
|
|
62
|
+
await currentFrame(page)
|
|
63
|
+
.locator(render(selector, ...scopesOf(apiContext, env)))
|
|
64
|
+
.first()
|
|
65
|
+
.click();
|
|
66
|
+
});
|
|
67
|
+
When('I click the frame {role} {string}', async ({ page, apiContext, env }, role, name) => {
|
|
68
|
+
await currentFrame(page)
|
|
69
|
+
.getByRole(role, { name: render(name, ...scopesOf(apiContext, env)) })
|
|
70
|
+
.first()
|
|
71
|
+
.click();
|
|
72
|
+
});
|
|
73
|
+
/* ── asserting inside the frame ───────────────────────────────────────── */
|
|
74
|
+
Then('the frame should contain the text {string}', async ({ page, apiContext, env }, text) => {
|
|
75
|
+
await expect(currentFrame(page)
|
|
76
|
+
.getByText(render(text, ...scopesOf(apiContext, env)))
|
|
77
|
+
.first()).toBeVisible();
|
|
78
|
+
});
|
|
79
|
+
Then('the frame element {string} should be visible', async ({ page, apiContext, env }, selector) => {
|
|
80
|
+
await expect(currentFrame(page)
|
|
81
|
+
.locator(render(selector, ...scopesOf(apiContext, env)))
|
|
82
|
+
.first()).toBeVisible();
|
|
83
|
+
});
|
|
84
|
+
Then('the page should have {int} frame(s)', async ({ page }, count) => {
|
|
85
|
+
// `page.frames()` includes the main frame; the count a scenario means is the
|
|
86
|
+
// number of embedded documents, so the main frame is excluded.
|
|
87
|
+
await expect
|
|
88
|
+
.poll(() => page.frames().length - 1, {
|
|
89
|
+
message: `the page should embed ${count} frame(s)`,
|
|
90
|
+
})
|
|
91
|
+
.toBe(count);
|
|
92
|
+
});
|
|
93
|
+
//# sourceMappingURL=iframe.steps.js.map
|
package/dist/steps/index.d.ts
CHANGED
|
@@ -7,6 +7,16 @@ import './api.steps.js';
|
|
|
7
7
|
import './ui.steps.js';
|
|
8
8
|
import './data.steps.js';
|
|
9
9
|
import './hybrid.steps.js';
|
|
10
|
+
import './iframe.steps.js';
|
|
11
|
+
import './tabs.steps.js';
|
|
12
|
+
import './db.steps.js';
|
|
13
|
+
import './clock.steps.js';
|
|
14
|
+
import './webhook.steps.js';
|
|
15
|
+
import './a11y.steps.js';
|
|
16
|
+
import './browser.steps.js';
|
|
17
|
+
import './dom.steps.js';
|
|
18
|
+
import './net.steps.js';
|
|
19
|
+
import './perf.steps.js';
|
|
10
20
|
import '../shots/hooks.js';
|
|
11
|
-
export { coreStepsGlob, coreStepsDir } from './glob.js';
|
|
21
|
+
export { coreStepsGlob, coreStepsPatterns, coreStepNames, coreStepsDir } from './glob.js';
|
|
12
22
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/steps/index.js
CHANGED
|
@@ -7,6 +7,16 @@ import './api.steps.js';
|
|
|
7
7
|
import './ui.steps.js';
|
|
8
8
|
import './data.steps.js';
|
|
9
9
|
import './hybrid.steps.js';
|
|
10
|
+
import './iframe.steps.js';
|
|
11
|
+
import './tabs.steps.js';
|
|
12
|
+
import './db.steps.js';
|
|
13
|
+
import './clock.steps.js';
|
|
14
|
+
import './webhook.steps.js';
|
|
15
|
+
import './a11y.steps.js';
|
|
16
|
+
import './browser.steps.js';
|
|
17
|
+
import './dom.steps.js';
|
|
18
|
+
import './net.steps.js';
|
|
19
|
+
import './perf.steps.js';
|
|
10
20
|
import '../shots/hooks.js';
|
|
11
|
-
export { coreStepsGlob, coreStepsDir } from './glob.js';
|
|
21
|
+
export { coreStepsGlob, coreStepsPatterns, coreStepNames, coreStepsDir } from './glob.js';
|
|
12
22
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import './params.js';
|
|
2
|
+
import type { APIRequestContext, TestInfo } from '@playwright/test';
|
|
3
|
+
import type { EnvConfig } from '@sdods/contracts';
|
|
4
|
+
import type { ResolvedConfig } from '../config/resolve.js';
|
|
5
|
+
import type { ApiContext } from '../fixtures/api-context.js';
|
|
6
|
+
export interface RawSnapshot {
|
|
7
|
+
method: string;
|
|
8
|
+
url: string;
|
|
9
|
+
requestBody?: string;
|
|
10
|
+
status: number;
|
|
11
|
+
statusText: string;
|
|
12
|
+
/** Lower-cased, comma-joined by Playwright for repeats — use `setCookies` for Set-Cookie. */
|
|
13
|
+
headers: Record<string, string>;
|
|
14
|
+
/** Every individual Set-Cookie line, which `headers()` would have collapsed into one string. */
|
|
15
|
+
setCookies: string[];
|
|
16
|
+
body: string;
|
|
17
|
+
}
|
|
18
|
+
export interface SendRawOptions {
|
|
19
|
+
body?: string;
|
|
20
|
+
form?: Record<string, string>;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* One request with `maxRedirects: 0`. The whole point is to keep the 3xx instead of chasing it,
|
|
24
|
+
* and to keep `Set-Cookie` unredacted — the two things the recorded ApiSnapshot cannot carry.
|
|
25
|
+
*/
|
|
26
|
+
export declare function sendRaw(deps: {
|
|
27
|
+
request: APIRequestContext;
|
|
28
|
+
apiContext: ApiContext;
|
|
29
|
+
env: EnvConfig;
|
|
30
|
+
config: ResolvedConfig;
|
|
31
|
+
testInfo?: Pick<TestInfo, 'attach'>;
|
|
32
|
+
}, method: string, pathOrUrl: string, opts?: SendRawOptions): Promise<RawSnapshot>;
|
|
33
|
+
export declare function redactForReport(headers: Record<string, string>): Record<string, string>;
|
|
34
|
+
export interface StreamEvent {
|
|
35
|
+
/** The SSE `event:` field, else the parsed JSON payload's `type`, else the raw sentinel. */
|
|
36
|
+
type: string | undefined;
|
|
37
|
+
data: string;
|
|
38
|
+
json?: unknown;
|
|
39
|
+
}
|
|
40
|
+
export interface StreamCapture {
|
|
41
|
+
status: number;
|
|
42
|
+
contentType: string;
|
|
43
|
+
events: StreamEvent[];
|
|
44
|
+
/** The body ended on a frame boundary — a stream cut mid-frame does not. */
|
|
45
|
+
endedWithDelimiter: boolean;
|
|
46
|
+
/** Transport failure (reset, timeout). Present means the stream did not finish. */
|
|
47
|
+
error?: string;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Parse an SSE body into frames. Frames are separated by a blank line; `data:` lines within one
|
|
51
|
+
* frame join with a newline; a frame carrying only a comment (`: keepalive`) is not an event.
|
|
52
|
+
*/
|
|
53
|
+
export declare function parseEventStream(text: string): Pick<StreamCapture, 'events' | 'endedWithDelimiter'>;
|
|
54
|
+
/**
|
|
55
|
+
* Playwright `page.route` glob semantics, restricted to the three operators that matter:
|
|
56
|
+
* `**` matches anything including `/`, `*` matches anything except `/`, `?` matches one
|
|
57
|
+
* non-`/` character. Anchored, like Playwright's own matcher. Kept deliberately compatible so a
|
|
58
|
+
* counting step and a `I mock {string} …` step can be given the same string.
|
|
59
|
+
*/
|
|
60
|
+
export declare function globToRegExp(glob: string): RegExp;
|
|
61
|
+
/** Every credential shape found in `text`, named. Exported so the patterns are testable. */
|
|
62
|
+
export declare function credentialFindings(text: string): string[];
|
|
63
|
+
//# sourceMappingURL=net.steps.d.ts.map
|