@sdods/core 0.9.0 → 0.10.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.
@@ -43,6 +43,27 @@ export const REPORT_WAIT_MS = PAINT_WAIT_MS + LCP_SETTLE_MS + 1_000;
43
43
  /** The binding each document reports through. Namespaced so it cannot collide with the app. */
44
44
  export const VITALS_BINDING = '__sdodsReportVitals';
45
45
  const watches = new WeakMap();
46
+ /**
47
+ * Resolves once the document has painted its first content, or after `paintWaitMs` when it never
48
+ * does. Runs in the page: it is serialised into the init script below and into `waitForSettle`, so
49
+ * the `@perf` and `@a11y` hooks agree on when a page has settled.
50
+ */
51
+ export function firstPaintOrTimeout(paintWaitMs) {
52
+ return new Promise((resolve) => {
53
+ if (performance.getEntriesByName('first-contentful-paint').length)
54
+ return resolve();
55
+ setTimeout(resolve, paintWaitMs);
56
+ try {
57
+ new PerformanceObserver((list) => {
58
+ if (list.getEntries().some((e) => e.name === 'first-contentful-paint'))
59
+ resolve();
60
+ }).observe({ type: 'paint', buffered: true });
61
+ }
62
+ catch {
63
+ resolve();
64
+ }
65
+ });
66
+ }
46
67
  /**
47
68
  * The script every document in the page runs. Only the top-level document reports: after its load
48
69
  * event, once it has painted (or `paintWaitMs` has passed), and after the same LCP settle window the
@@ -52,20 +73,10 @@ export function vitalsInitScript(settleMs = LCP_SETTLE_MS, paintWaitMs = PAINT_W
52
73
  return `(() => {
53
74
  if (window.top !== window) return;
54
75
  const collect = ${collectRawVitals.toString()};
55
- const painted = () => new Promise((resolve) => {
56
- if (performance.getEntriesByName('first-contentful-paint').length) return resolve();
57
- setTimeout(resolve, ${paintWaitMs});
58
- try {
59
- new PerformanceObserver((list) => {
60
- if (list.getEntries().some((e) => e.name === 'first-contentful-paint')) resolve();
61
- }).observe({ type: 'paint', buffered: true });
62
- } catch (e) {
63
- resolve();
64
- }
65
- });
76
+ const painted = ${firstPaintOrTimeout.toString()};
66
77
  addEventListener('load', () => {
67
78
  setTimeout(() => {
68
- painted()
79
+ painted(${paintWaitMs})
69
80
  .then(() => collect(${settleMs}))
70
81
  .then((raw) => {
71
82
  const report = window[${JSON.stringify(binding)}];
@@ -75,6 +86,19 @@ export function vitalsInitScript(settleMs = LCP_SETTLE_MS, paintWaitMs = PAINT_W
75
86
  }, { once: true });
76
87
  })();`;
77
88
  }
89
+ /**
90
+ * Waits, from Node, for the point at which the `@perf` init script reads a document: its load event
91
+ * (bounded by `LOAD_WAIT_MS`), its first contentful paint (bounded by `PAINT_WAIT_MS`), then the LCP
92
+ * settle window. Never throws: a page that will not settle is audited as it stands, and a page that
93
+ * navigates away while this waits is the caller's to notice.
94
+ */
95
+ export async function waitForSettle(page, opts = {}) {
96
+ const { paintWaitMs = PAINT_WAIT_MS, settleMs = LCP_SETTLE_MS, loadWaitMs = LOAD_WAIT_MS } = opts;
97
+ await page.waitForLoadState('load', { timeout: loadWaitMs }).catch(() => undefined);
98
+ await page
99
+ .evaluate(`(${firstPaintOrTimeout.toString()})(${paintWaitMs}).then(() => new Promise((r) => setTimeout(r, ${settleMs})))`)
100
+ .catch(() => undefined);
101
+ }
78
102
  /**
79
103
  * Starts recording the vitals of every document the page's main frame loads. Must run before the
80
104
  * scenario's first navigation, which is why the hook that calls it is a `BeforeScenario`.
@@ -73,7 +73,7 @@ export function listVisualFailures(runDir) {
73
73
  }
74
74
  for (const n of names) {
75
75
  const abs = join(dir, n);
76
- let isDir = false;
76
+ let isDir;
77
77
  try {
78
78
  isDir = statSync(abs).isDirectory();
79
79
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sdods/core",
3
- "version": "0.9.0",
3
+ "version": "0.10.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>",
@@ -43,12 +43,12 @@
43
43
  "playwright-bdd": ">=9"
44
44
  },
45
45
  "dependencies": {
46
- "@sdods/contracts": "0.9.0",
46
+ "@sdods/contracts": "0.10.0",
47
47
  "@cucumber/cucumber-expressions": "19.0.0",
48
48
  "@cucumber/gherkin": "^42.0.1",
49
49
  "@cucumber/messages": "^34.2.1",
50
50
  "@faker-js/faker": "^10.6.0",
51
- "@scalar/openapi-parser": "^0.29.0",
51
+ "@scalar/openapi-parser": "^0.29.1",
52
52
  "ajv": "^8.20.0",
53
53
  "ajv-formats": "^3.0.1",
54
54
  "csv-parse": "^7.0.2",
@@ -58,10 +58,10 @@
58
58
  "jsonpath-plus": "^10.4.0",
59
59
  "picocolors": "^1.1.1",
60
60
  "ts-morph": "^28.0.0",
61
- "tsx": "^4.23.0",
62
- "yaml": "^2.9.0",
63
- "zod": "^4.5.4",
64
- "@sdods/db": "0.9.0"
61
+ "tsx": "^4.23.13",
62
+ "yaml": "^2.9.1",
63
+ "zod": "^4.6.5",
64
+ "@sdods/db": "0.10.0"
65
65
  },
66
66
  "homepage": "https://sdods.com",
67
67
  "bugs": {