@pygmalionjs/pygmalion 0.2.18 → 0.2.19
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/README.md +20 -0
- package/node/storyboard-capture-runtime.mjs +37 -7
- package/package.json +1 -1
- package/storyboard.d.ts +19 -7
package/README.md
CHANGED
|
@@ -154,6 +154,26 @@ unapplied edits, `switchSource` refuses rather than leave the canvas on a
|
|
|
154
154
|
revision those edits were never written against — apply or revert them first, or
|
|
155
155
|
pass `{ discardEdits: true }`.
|
|
156
156
|
|
|
157
|
+
### Screens that never stop moving
|
|
158
|
+
|
|
159
|
+
A capture waits for the document to hold still. A screen with an elapsed clock, a
|
|
160
|
+
level meter, or a marquee never does, so it fails on `stabilize` no matter how
|
|
161
|
+
long it waits. Declare those regions and their mutations stop counting as churn:
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
captureStoryboardCase({
|
|
165
|
+
// ...
|
|
166
|
+
stability: {
|
|
167
|
+
volatileSelectors: ['[data-testid="elapsed-time"]', '[data-live-meter]'],
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
The wait itself samples every 100ms and needs three identical samples, up to a
|
|
173
|
+
4s ceiling — wide enough for a screen that repaints on a one-second beat.
|
|
174
|
+
`attempts`, `requiredStableSamples`, `intervalMs`, and `minimumWaitMs` are
|
|
175
|
+
adjustable for anything slower.
|
|
176
|
+
|
|
157
177
|
## Register screens and scenarios
|
|
158
178
|
|
|
159
179
|
The host owns application-specific routes, fixtures, authentication, mock data,
|
|
@@ -651,22 +651,43 @@ export function collectStoryboardDomTree() {
|
|
|
651
651
|
* This function is self-contained because Playwright serializes it into the
|
|
652
652
|
* page.
|
|
653
653
|
*/
|
|
654
|
-
export function storyboardDocumentStabilitySignature() {
|
|
654
|
+
export function storyboardDocumentStabilitySignature(volatileSelectors = []) {
|
|
655
655
|
const runtime = window;
|
|
656
656
|
const trackerKey = '__PYGMALION_STORYBOARD_STABILITY__';
|
|
657
|
+
const selectors = Array.isArray(volatileSelectors) ? volatileSelectors : [];
|
|
658
|
+
const selectorKey = selectors.join(',');
|
|
657
659
|
let tracker = runtime[trackerKey];
|
|
658
|
-
if (!tracker || tracker.document !== document) {
|
|
660
|
+
if (!tracker || tracker.document !== document || tracker.selectorKey !== selectorKey) {
|
|
659
661
|
tracker?.observer?.disconnect?.();
|
|
660
662
|
tracker = {
|
|
661
663
|
document,
|
|
664
|
+
selectorKey,
|
|
662
665
|
observer: null,
|
|
663
666
|
revision: 0,
|
|
664
667
|
};
|
|
668
|
+
// A screen can hold something that never stops changing — an elapsed clock, a
|
|
669
|
+
// level meter, a marquee. Counting those makes the document look unstable
|
|
670
|
+
// forever, so a caller can declare them and have their mutations ignored.
|
|
671
|
+
const isVolatile = (node) => {
|
|
672
|
+
if (!selectorKey) return false;
|
|
673
|
+
const element =
|
|
674
|
+
node && node.nodeType === 1 ? node : (node && node.parentElement) || null;
|
|
675
|
+
if (!element || typeof element.closest !== 'function') return false;
|
|
676
|
+
try {
|
|
677
|
+
return element.closest(selectorKey) != null;
|
|
678
|
+
} catch {
|
|
679
|
+
return false;
|
|
680
|
+
}
|
|
681
|
+
};
|
|
665
682
|
if (
|
|
666
683
|
document.documentElement &&
|
|
667
684
|
typeof runtime.MutationObserver === 'function'
|
|
668
685
|
) {
|
|
669
|
-
tracker.observer = new runtime.MutationObserver(() => {
|
|
686
|
+
tracker.observer = new runtime.MutationObserver((records) => {
|
|
687
|
+
const batch = Array.isArray(records) ? records : [];
|
|
688
|
+
if (batch.length > 0 && batch.every((record) => isVolatile(record.target))) {
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
670
691
|
tracker.revision += 1;
|
|
671
692
|
});
|
|
672
693
|
tracker.observer.observe(document.documentElement, {
|
|
@@ -724,10 +745,13 @@ export function storyboardDocumentStabilitySignature() {
|
|
|
724
745
|
export async function waitForStableStoryboardDocument(
|
|
725
746
|
page,
|
|
726
747
|
{
|
|
727
|
-
|
|
748
|
+
// A screen that repaints on a one-second beat needs a window wider than that
|
|
749
|
+
// beat to show a quiet stretch, so the ceiling is 4s rather than 2s.
|
|
750
|
+
attempts = 40,
|
|
728
751
|
requiredStableSamples = 3,
|
|
729
752
|
intervalMs = 100,
|
|
730
753
|
minimumWaitMs = 400,
|
|
754
|
+
volatileSelectors = [],
|
|
731
755
|
} = {},
|
|
732
756
|
) {
|
|
733
757
|
await page.evaluate(async () => {
|
|
@@ -737,7 +761,10 @@ export async function waitForStableStoryboardDocument(
|
|
|
737
761
|
let stable = 0;
|
|
738
762
|
let elapsedMs = 0;
|
|
739
763
|
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
740
|
-
const current = await page.evaluate(
|
|
764
|
+
const current = await page.evaluate(
|
|
765
|
+
storyboardDocumentStabilitySignature,
|
|
766
|
+
volatileSelectors,
|
|
767
|
+
);
|
|
741
768
|
if (current === previous) stable += 1;
|
|
742
769
|
else stable = 0;
|
|
743
770
|
previous = current;
|
|
@@ -890,13 +917,14 @@ async function collectStableEvidence(
|
|
|
890
917
|
includePreviewSnapshot,
|
|
891
918
|
previewBaseToken,
|
|
892
919
|
screenshotOptions,
|
|
920
|
+
stability = {},
|
|
893
921
|
},
|
|
894
922
|
) {
|
|
895
923
|
const evidence = {};
|
|
896
924
|
const errors = [];
|
|
897
925
|
try {
|
|
898
926
|
await page.addStyleTag({ content: FROZEN_STYLE });
|
|
899
|
-
const stable = await waitForStableStoryboardDocument(page);
|
|
927
|
+
const stable = await waitForStableStoryboardDocument(page, stability);
|
|
900
928
|
if (!stable) {
|
|
901
929
|
throw new Error(
|
|
902
930
|
'The rendered document did not reach a stable DOM and overlay state.',
|
|
@@ -986,6 +1014,7 @@ export async function captureStoryboardCase({
|
|
|
986
1014
|
contextOptions = {},
|
|
987
1015
|
navigationOptions = {},
|
|
988
1016
|
screenshotOptions = {},
|
|
1017
|
+
stability = {},
|
|
989
1018
|
} = {}) {
|
|
990
1019
|
if (!browser || typeof browser.newContext !== 'function') {
|
|
991
1020
|
throw new TypeError(
|
|
@@ -1081,7 +1110,7 @@ export async function captureStoryboardCase({
|
|
|
1081
1110
|
}
|
|
1082
1111
|
await atCaptureStage('stabilize', async () => {
|
|
1083
1112
|
await page.addStyleTag({ content: FROZEN_STYLE });
|
|
1084
|
-
const stable = await waitForStableStoryboardDocument(page);
|
|
1113
|
+
const stable = await waitForStableStoryboardDocument(page, stability);
|
|
1085
1114
|
if (!stable) {
|
|
1086
1115
|
throw new Error(
|
|
1087
1116
|
'The rendered document did not reach a stable DOM and overlay state.',
|
|
@@ -1104,6 +1133,7 @@ export async function captureStoryboardCase({
|
|
|
1104
1133
|
includePreviewSnapshot,
|
|
1105
1134
|
previewBaseToken,
|
|
1106
1135
|
screenshotOptions,
|
|
1136
|
+
stability,
|
|
1107
1137
|
});
|
|
1108
1138
|
evidence = collected.evidence;
|
|
1109
1139
|
evidenceErrors.push(...collected.errors);
|
package/package.json
CHANGED
package/storyboard.d.ts
CHANGED
|
@@ -66,6 +66,21 @@ export interface StoryboardCaptureOptions<
|
|
|
66
66
|
contextOptions?: Record<string, unknown>;
|
|
67
67
|
navigationOptions?: Record<string, unknown>;
|
|
68
68
|
screenshotOptions?: Record<string, unknown>;
|
|
69
|
+
/** How long to wait for a still document, and what may keep moving. */
|
|
70
|
+
stability?: StoryboardStabilityOptions;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface StoryboardStabilityOptions {
|
|
74
|
+
/** Samples before giving up. Default 40 at 100ms, so a 4s ceiling. */
|
|
75
|
+
attempts?: number;
|
|
76
|
+
requiredStableSamples?: number;
|
|
77
|
+
intervalMs?: number;
|
|
78
|
+
minimumWaitMs?: number;
|
|
79
|
+
/**
|
|
80
|
+
* Regions that are expected to keep changing — an elapsed clock, a level
|
|
81
|
+
* meter, a marquee. Mutations confined to them do not count as churn.
|
|
82
|
+
*/
|
|
83
|
+
volatileSelectors?: readonly string[];
|
|
69
84
|
}
|
|
70
85
|
|
|
71
86
|
export declare const STORYBOARD_CAPTURE_STATUSES: Readonly<{
|
|
@@ -132,14 +147,11 @@ export declare function assertFinalStoryboardState(
|
|
|
132
147
|
): Promise<void>;
|
|
133
148
|
export declare function waitForStableStoryboardDocument(
|
|
134
149
|
page: unknown,
|
|
135
|
-
options?:
|
|
136
|
-
attempts?: number;
|
|
137
|
-
requiredStableSamples?: number;
|
|
138
|
-
intervalMs?: number;
|
|
139
|
-
minimumWaitMs?: number;
|
|
140
|
-
},
|
|
150
|
+
options?: StoryboardStabilityOptions,
|
|
141
151
|
): Promise<boolean>;
|
|
142
|
-
export declare function storyboardDocumentStabilitySignature(
|
|
152
|
+
export declare function storyboardDocumentStabilitySignature(
|
|
153
|
+
volatileSelectors?: readonly string[],
|
|
154
|
+
): string;
|
|
143
155
|
export declare function storyboardCaptureCandidateTexts(
|
|
144
156
|
element: Element,
|
|
145
157
|
): string[];
|