@sdods/core 0.2.1 → 0.3.0

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 (55) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/analyze/detectors.js +236 -24
  3. package/dist/analyze/propose.js +32 -10
  4. package/dist/analyze/scan.js +19 -1
  5. package/dist/api/client.js +7 -1
  6. package/dist/auth/capture.js +21 -7
  7. package/dist/auth/index.js +71 -12
  8. package/dist/config/resolve.d.ts +13 -0
  9. package/dist/config/resolve.js +1 -0
  10. package/dist/config/runner.js +3 -0
  11. package/dist/config/tags.d.ts +29 -1
  12. package/dist/config/tags.js +46 -0
  13. package/dist/data/provider.js +5 -1
  14. package/dist/data/user-pool.js +35 -3
  15. package/dist/fixtures/api-context.d.ts +14 -1
  16. package/dist/fixtures/api-context.js +13 -0
  17. package/dist/fixtures/auth.d.ts +9 -1
  18. package/dist/fixtures/auth.js +13 -5
  19. package/dist/fixtures/test.js +42 -1
  20. package/dist/fixtures/types.d.ts +2 -0
  21. package/dist/har/api-har.d.ts +1 -0
  22. package/dist/har/api-har.js +1 -1
  23. package/dist/har/index.d.ts +1 -0
  24. package/dist/har/index.js +1 -0
  25. package/dist/har/scrub.d.ts +17 -0
  26. package/dist/har/scrub.js +60 -0
  27. package/dist/reporters/dashboard.d.ts +86 -0
  28. package/dist/reporters/dashboard.js +319 -61
  29. package/dist/steps/a11y.steps.d.ts +180 -0
  30. package/dist/steps/a11y.steps.js +598 -0
  31. package/dist/steps/api.steps.js +5 -1
  32. package/dist/steps/browser.steps.d.ts +27 -0
  33. package/dist/steps/browser.steps.js +653 -0
  34. package/dist/steps/clock.steps.d.ts +4 -0
  35. package/dist/steps/clock.steps.js +73 -0
  36. package/dist/steps/data.steps.js +50 -2
  37. package/dist/steps/db.steps.d.ts +5 -0
  38. package/dist/steps/db.steps.js +105 -0
  39. package/dist/steps/dom.steps.d.ts +2 -0
  40. package/dist/steps/dom.steps.js +583 -0
  41. package/dist/steps/iframe.steps.d.ts +2 -0
  42. package/dist/steps/iframe.steps.js +93 -0
  43. package/dist/steps/index.d.ts +10 -0
  44. package/dist/steps/index.js +10 -0
  45. package/dist/steps/net.steps.d.ts +63 -0
  46. package/dist/steps/net.steps.js +728 -0
  47. package/dist/steps/perf.steps.d.ts +248 -0
  48. package/dist/steps/perf.steps.js +514 -0
  49. package/dist/steps/tabs.steps.d.ts +5 -0
  50. package/dist/steps/tabs.steps.js +109 -0
  51. package/dist/steps/webhook.steps.d.ts +46 -0
  52. package/dist/steps/webhook.steps.js +129 -0
  53. package/dist/version.d.ts +1 -1
  54. package/dist/version.js +1 -1
  55. package/package.json +3 -4
@@ -0,0 +1,46 @@
1
+ import { type Server } from 'node:http';
2
+ import './params.js';
3
+ /**
4
+ * An ephemeral callback receiver, for the leg where the application calls BACK.
5
+ *
6
+ * WHY — 16 trigger providers deliver by webhook. Without a receiver a suite can
7
+ * assert that a subscription was created and nothing about whether a delivery
8
+ * ever arrived, which is the half that actually matters: a trigger that
9
+ * registers and never fires looks identical to a working one.
10
+ *
11
+ * DESIGN — the server binds to an EPHEMERAL port on 127.0.0.1 and its URL is
12
+ * published into the scenario's template scope as `{{callback.url}}`, so the
13
+ * scenario never hardcodes a port. It is torn down at scenario end even when
14
+ * the scenario fails; a leaked listener silently poisons the next run on the
15
+ * same worker, and that is the kind of failure nobody traces back.
16
+ *
17
+ * Deliveries are recorded in arrival order and asserted by POLLING, never by
18
+ * sleeping. "Wait two seconds then check" is how a webhook suite becomes both
19
+ * slow and flaky at the same time.
20
+ *
21
+ * NOT a public tunnel. This receives from an application that can reach the
22
+ * runner — a local or in-VPC app under test. Reaching a hosted staging
23
+ * deployment needs a tunnel, which is an infrastructure decision rather than
24
+ * something a step library should quietly stand up.
25
+ */
26
+ export interface Delivery {
27
+ method: string;
28
+ path: string;
29
+ headers: Record<string, string>;
30
+ body: string;
31
+ receivedAt: number;
32
+ }
33
+ export interface Receiver {
34
+ server: Server;
35
+ url: string;
36
+ deliveries: Delivery[];
37
+ }
38
+ /**
39
+ * Extracted from the step so the receiver itself is testable without a browser,
40
+ * a config or a Playwright runner. It is the most intricate piece here — an
41
+ * HTTP server, async body reading and a shared array — and "it worked when I
42
+ * tried it" is not a claim anyone can re-check later.
43
+ */
44
+ export declare function startReceiver(): Promise<Receiver>;
45
+ export declare function closeAllReceivers(): Promise<void>;
46
+ //# sourceMappingURL=webhook.steps.d.ts.map
@@ -0,0 +1,129 @@
1
+ import { createServer } from 'node:http';
2
+ import { expect } from '@playwright/test';
3
+ import './params.js';
4
+ import { AfterScenario, Given, Then, When } from '../fixtures/test.js';
5
+ import { render } from '../api/template.js';
6
+ import { SdodsError } from '../errors.js';
7
+ const receivers = new Map();
8
+ function key(apiContext) {
9
+ return String(apiContext.runId ?? 'default');
10
+ }
11
+ async function readBody(req) {
12
+ const chunks = [];
13
+ for await (const chunk of req)
14
+ chunks.push(chunk);
15
+ return Buffer.concat(chunks).toString('utf8');
16
+ }
17
+ function requireReceiver(k) {
18
+ const r = receivers.get(k);
19
+ if (!r) {
20
+ throw new SdodsError('NOT_SUPPORTED', 'No callback receiver is listening.', {
21
+ hint: 'Use `Given a callback receiver is listening` first; its URL is available as {{callback.url}}.',
22
+ });
23
+ }
24
+ return r;
25
+ }
26
+ /**
27
+ * Extracted from the step so the receiver itself is testable without a browser,
28
+ * a config or a Playwright runner. It is the most intricate piece here — an
29
+ * HTTP server, async body reading and a shared array — and "it worked when I
30
+ * tried it" is not a claim anyone can re-check later.
31
+ */
32
+ export async function startReceiver() {
33
+ const deliveries = [];
34
+ const server = createServer((req, res) => {
35
+ void readBody(req).then((body) => {
36
+ deliveries.push({
37
+ method: req.method ?? 'GET',
38
+ path: req.url ?? '/',
39
+ headers: Object.fromEntries(Object.entries(req.headers).map(([h, v]) => [
40
+ h,
41
+ Array.isArray(v) ? v.join(', ') : (v ?? ''),
42
+ ])),
43
+ body,
44
+ receivedAt: Date.now(),
45
+ });
46
+ // 200 with an empty body: a provider that retries on a non-2xx would
47
+ // otherwise deliver the same event repeatedly and the counts below would
48
+ // be meaningless.
49
+ res.writeHead(200, { 'content-type': 'application/json' });
50
+ res.end('{}');
51
+ });
52
+ });
53
+ await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
54
+ const address = server.address();
55
+ if (!address || typeof address === 'string') {
56
+ throw new SdodsError('INTERNAL', 'The callback receiver did not report a port.');
57
+ }
58
+ return { server, url: `http://127.0.0.1:${address.port}`, deliveries };
59
+ }
60
+ Given('a callback receiver is listening', async ({ apiContext }) => {
61
+ const k = key(apiContext);
62
+ if (receivers.has(k))
63
+ return;
64
+ const receiver = await startReceiver();
65
+ receivers.set(k, receiver);
66
+ // Published as a scope value so the scenario writes {{callback.url}} rather
67
+ // than a port it cannot know.
68
+ apiContext.vars.set('callback', {
69
+ url: receiver.url,
70
+ });
71
+ });
72
+ When('I stop the callback receiver', async ({ apiContext }) => {
73
+ const k = key(apiContext);
74
+ const r = receivers.get(k);
75
+ if (!r)
76
+ return;
77
+ await new Promise((resolve) => r.server.close(() => resolve()));
78
+ receivers.delete(k);
79
+ });
80
+ Then('the callback receiver should receive {int} delivery/deliveries', async ({ apiContext }, count) => {
81
+ const r = requireReceiver(key(apiContext));
82
+ await expect
83
+ .poll(() => r.deliveries.length, {
84
+ message: `expected ${count} callback delivery/deliveries`,
85
+ })
86
+ .toBe(count);
87
+ });
88
+ Then('the callback receiver should receive a delivery', async ({ apiContext }) => {
89
+ const r = requireReceiver(key(apiContext));
90
+ await expect
91
+ .poll(() => r.deliveries.length, { message: 'no callback delivery arrived' })
92
+ .toBeGreaterThan(0);
93
+ });
94
+ Then('the callback receiver should receive a delivery containing {string}', async ({ apiContext, env }, text) => {
95
+ const r = requireReceiver(key(apiContext));
96
+ const needle = render(text, apiContext.vars.toObject(), env.vars);
97
+ await expect
98
+ .poll(() => r.deliveries.some((d) => d.body.includes(needle)), {
99
+ message: `no callback delivery contained "${needle}"`,
100
+ })
101
+ .toBe(true);
102
+ });
103
+ Then('the last callback delivery should have the header {string} set to {string}', async ({ apiContext, env }, header, value) => {
104
+ const r = requireReceiver(key(apiContext));
105
+ await expect
106
+ .poll(() => r.deliveries.length, { message: 'no callback delivery arrived' })
107
+ .toBeGreaterThan(0);
108
+ const scopes = [
109
+ apiContext.vars.toObject(),
110
+ env.vars,
111
+ ];
112
+ const last = r.deliveries[r.deliveries.length - 1];
113
+ expect(last.headers[render(header, ...scopes).toLowerCase()]).toBe(render(value, ...scopes));
114
+ });
115
+ /**
116
+ * Torn down after EVERY scenario, including a failing one — hence a hook rather
117
+ * than a step. A leaked listener silently poisons the next scenario on the same
118
+ * worker, and that is a failure nobody traces back to the scenario that leaked.
119
+ */
120
+ AfterScenario(async () => {
121
+ await closeAllReceivers();
122
+ });
123
+ export async function closeAllReceivers() {
124
+ for (const [k, r] of receivers) {
125
+ await new Promise((resolve) => r.server.close(() => resolve()));
126
+ receivers.delete(k);
127
+ }
128
+ }
129
+ //# sourceMappingURL=webhook.steps.js.map
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "0.2.1";
1
+ export declare const VERSION = "0.2.2";
2
2
  //# sourceMappingURL=version.d.ts.map
package/dist/version.js CHANGED
@@ -1,2 +1,2 @@
1
- export const VERSION = '0.2.1';
1
+ export const VERSION = '0.2.2';
2
2
  //# sourceMappingURL=version.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sdods/core",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "SDODS runtime: configuration, project registry, fixtures, step libraries, data providers, screenshot narratives and self-healing locators.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "SDODS <admin@sdods.com>",
@@ -42,14 +42,13 @@
42
42
  "playwright-bdd": ">=9"
43
43
  },
44
44
  "dependencies": {
45
- "@sdods/contracts": "0.2.1",
45
+ "@sdods/contracts": "0.3.0",
46
46
  "@cucumber/gherkin": "^42.0.1",
47
47
  "@cucumber/messages": "^34.2.1",
48
48
  "@faker-js/faker": "^10.6.0",
49
49
  "@scalar/openapi-parser": "^0.29.0",
50
50
  "ajv": "^8.20.0",
51
51
  "ajv-formats": "^3.0.1",
52
- "chart.js": "^4.5.1",
53
52
  "csv-parse": "^7.0.2",
54
53
  "cucumber-tag-expressions": "^2.0.3",
55
54
  "dotenv": "^17.4.2",
@@ -59,7 +58,7 @@
59
58
  "ts-morph": "^28.0.0",
60
59
  "yaml": "^2.9.0",
61
60
  "zod": "^4.5.4",
62
- "@sdods/db": "0.2.1"
61
+ "@sdods/db": "0.3.0"
63
62
  },
64
63
  "homepage": "https://sdods.com",
65
64
  "bugs": {