@ralphralphai/playwright 1.7.0 → 1.8.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.
- package/README.md +16 -0
- package/dist/index.cjs +3 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +47 -1
- package/dist/index.mjs +3 -3
- package/dist/index.mjs.map +1 -1
- package/dist/reporter.cjs +1 -1
- package/dist/reporter.mjs +1 -1
- package/dist/{upload-ZMnTCjs2.cjs → upload-BvAi7mMY.cjs} +501 -8
- package/dist/upload-BvAi7mMY.cjs.map +1 -0
- package/dist/{upload-DQn3CLvx.mjs → upload-D5hePvmA.mjs} +501 -8
- package/dist/upload-D5hePvmA.mjs.map +1 -0
- package/package.json +1 -1
- package/dist/upload-DQn3CLvx.mjs.map +0 -1
- package/dist/upload-ZMnTCjs2.cjs.map +0 -1
package/README.md
CHANGED
|
@@ -176,6 +176,22 @@ await ralph.waitForCapture();
|
|
|
176
176
|
await context.close();
|
|
177
177
|
```
|
|
178
178
|
|
|
179
|
+
### What happened between two nodes
|
|
180
|
+
|
|
181
|
+
The Playwright actions a test takes on the way from one node to the next are
|
|
182
|
+
recorded on the edge between them: clicks, hovers, fills, key presses,
|
|
183
|
+
selects, uploads, drags, and `goto` / back / forward / reload. Each one keeps
|
|
184
|
+
the element it landed on (its role, name, and `data-track-id`) and where that
|
|
185
|
+
element was on the earlier node's screenshot, which Ralph shows as, e.g.,
|
|
186
|
+
`Click button "Checkout"`.
|
|
187
|
+
|
|
188
|
+
What was typed into a password or one-time-code field is never recorded.
|
|
189
|
+
Actions taken while recording is paused are left out.
|
|
190
|
+
|
|
191
|
+
Pass an `edgeLabel` to describe the step in your own words, e.g.
|
|
192
|
+
`ralph.recordNode(page, { name: 'Done', edgeLabel: 'Sign in' })`. A later call
|
|
193
|
+
without one keeps it.
|
|
194
|
+
|
|
179
195
|
### When recording fails
|
|
180
196
|
|
|
181
197
|
- A failed `recordNode` call rejects.
|
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_upload = require("./upload-
|
|
2
|
+
const require_upload = require("./upload-BvAi7mMY.cjs");
|
|
3
3
|
let node_path = require("node:path");
|
|
4
4
|
node_path = require_upload.__toESM(node_path, 1);
|
|
5
5
|
let _playwright_test = require("@playwright/test");
|
|
@@ -63,7 +63,7 @@ const test = _playwright_test.test.extend({
|
|
|
63
63
|
applyScreenSelection(testInfo);
|
|
64
64
|
await use();
|
|
65
65
|
}, { auto: true }],
|
|
66
|
-
_ralphRecorder: [async ({ ralphOptions, _ralphConfigs, _ralphScreen }, use, testInfo) => {
|
|
66
|
+
_ralphRecorder: [async ({ ralphOptions, _ralphConfigs, _ralphScreen, playwright }, use, testInfo) => {
|
|
67
67
|
const mode = require_upload.resolveMode(ralphOptions);
|
|
68
68
|
if (!testInfo.tags.includes("@ralph") || mode === "off") {
|
|
69
69
|
await use(null);
|
|
@@ -82,7 +82,7 @@ const test = _playwright_test.test.extend({
|
|
|
82
82
|
pending = require_upload.loadConfig(ralphOptions, root);
|
|
83
83
|
_ralphConfigs.set(key, pending);
|
|
84
84
|
}
|
|
85
|
-
const recorder = new require_upload.Recorder(ralphOptions, await pending, testInfo, mode);
|
|
85
|
+
const recorder = new require_upload.Recorder(ralphOptions, await pending, testInfo, mode, playwright);
|
|
86
86
|
try {
|
|
87
87
|
await use(recorder);
|
|
88
88
|
} finally {
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["loadConfigSync","base","resolveMode","path","loadConfig","Recorder"],"sources":["../src/screens.ts","../src/index.ts"],"sourcesContent":["import type {\n PlaywrightTestOptions,\n PlaywrightWorkerOptions,\n Project,\n TestInfo,\n} from '@playwright/test';\nimport type { ScreenSize } from '@ralphralphai/config';\n\nimport { loadConfigSync } from './config';\nimport type { RalphOptions } from './types';\n\ntype RalphProject = Project<\n PlaywrightTestOptions & { ralphOptions: RalphOptions },\n PlaywrightWorkerOptions\n>;\n\nexport const SCREEN_TAG_PREFIX = '@ralph-screen:';\n\nexport type RalphProjectOptions = Pick<\n RalphOptions,\n 'config' | 'configPath'\n> & {\n /** Resolves a relative `configPath`; defaults to the current directory. */\n root?: string;\n};\n\ntype ScreenMetadata = { screenSize: ScreenSize; screenSizes: ScreenSize[] };\n\nexport function screenLabel(size: ScreenSize): string {\n return size.width + 'x' + size.height;\n}\n\n/**\n * Expands `base` into one project per `screenSizes` entry of the Ralph config.\n * Each project's viewport is that screen size.\n */\nexport function ralphProjects(\n base: RalphProject = {},\n options: RalphProjectOptions = {},\n): RalphProject[] {\n const { screenSizes } = loadConfigSync(\n options,\n options.root ?? process.cwd(),\n ).config;\n if (!screenSizes.length) {\n throw new Error('The Ralph config declares no screenSizes.');\n }\n return screenSizes.map((screenSize) => {\n const label = screenLabel(screenSize);\n const ralph: ScreenMetadata = { screenSize, screenSizes };\n return {\n ...base,\n name: base.name ? base.name + ' ' + label : label,\n use: {\n ...base.use,\n viewport: { width: screenSize.width, height: screenSize.height },\n },\n metadata: { ...base.metadata, ralph },\n };\n });\n}\n\n/**\n * Skips a test whose `@ralph-screen:WxH` tags exclude the current project's\n * screen size. Untagged tests run at every size; other projects are untouched.\n */\nexport function applyScreenSelection(testInfo: TestInfo): void {\n const selected = testInfo.tags\n .filter((tag) => tag.startsWith(SCREEN_TAG_PREFIX))\n .map((tag) => tag.slice(SCREEN_TAG_PREFIX.length));\n const metadata = (testInfo.project.metadata as { ralph?: ScreenMetadata })\n .ralph;\n if (!selected.length || !metadata) {\n return;\n }\n const known = metadata.screenSizes.map(screenLabel);\n const unknown = selected.filter((label) => !known.includes(label));\n if (unknown.length) {\n throw new Error(\n 'Unknown Ralph screen size ' +\n unknown.join(', ') +\n '; the config declares ' +\n known.join(', ') +\n '.',\n );\n }\n const current = screenLabel(metadata.screenSize);\n testInfo.skip(\n !selected.includes(current),\n 'Not selected for Ralph screen size ' + current + '.',\n );\n}\n","import path from 'node:path';\n\nimport { test as base } from '@playwright/test';\nimport type {\n PlaywrightTestArgs,\n PlaywrightTestOptions,\n PlaywrightWorkerArgs,\n PlaywrightWorkerOptions,\n TestType,\n} from '@playwright/test';\n\nimport { loadConfig, resolveMode } from './config';\nimport { Recorder, REPORTER_ENV } from './recorder';\nimport { applyScreenSelection } from './screens';\nimport type { ConfigSnapshot, Ralph, RalphOptions } from './types';\n\nexport { expect, mergeTests } from '@playwright/test';\nexport type { WebRalphConfig } from '@ralphralphai/config';\nexport {\n createRawGraphBundle,\n prepareArtifactUpload,\n uploadRawGraph,\n} from './upload';\nexport type {\n PreparedUpload,\n UploadOptions,\n UploadReceipt,\n RawGraphScreenshot,\n RawGraphBundleIndex,\n} from './upload';\nexport { MANIFEST_ATTACHMENT } from './recorder';\nexport { ralphProjects, SCREEN_TAG_PREFIX } from './screens';\nexport type { RalphProjectOptions } from './screens';\nexport type { RalphReporterOptions } from './reporter';\nexport type {\n RawGraphManifest,\n RawGraph,\n RawGraphFile,\n RawPage,\n RawNode,\n ConditionsDeclaration,\n NodeConditions,\n RecordedTags,\n RecordNodeOptions,\n TagConditions,\n TagDeclaration,\n TagValue,\n ConfigSnapshot,\n PageLayout,\n Ralph,\n RalphOptions,\n TrackedElement,\n} from './types';\n\nexport type RalphFixtures = { ralph: Ralph; ralphOptions: RalphOptions };\n\ntype InternalFixtures = {\n _ralphScreen: void;\n _ralphRecorder: Recorder | null;\n};\ntype WorkerFixtures = { _ralphConfigs: Map<string, Promise<ConfigSnapshot>> };\n\nexport const test: TestType<\n PlaywrightTestArgs & PlaywrightTestOptions & RalphFixtures,\n PlaywrightWorkerArgs & PlaywrightWorkerOptions\n> = base.extend<RalphFixtures & InternalFixtures, WorkerFixtures>({\n ralphOptions: [{}, { option: true }],\n _ralphConfigs: [\n // Playwright requires destructuring here to discover fixture dependencies.\n // oxlint-disable-next-line no-empty-pattern\n async ({}, use) => {\n await use(new Map());\n },\n { scope: 'worker' },\n ],\n _ralphScreen: [\n // Playwright requires destructuring here to discover fixture dependencies.\n // oxlint-disable-next-line no-empty-pattern\n async ({}, use, testInfo) => {\n // Screen selection chooses which flows run, so it applies in every mode.\n applyScreenSelection(testInfo);\n await use();\n },\n { auto: true },\n ],\n _ralphRecorder: [\n // _ralphScreen is specified here so that the playwright treat that it is\n // a dependency of this fixture.\n async ({ ralphOptions, _ralphConfigs, _ralphScreen }, use, testInfo) => {\n const mode = resolveMode(ralphOptions);\n if (!testInfo.tags.includes('@ralph') || mode === 'off') {\n await use(null);\n return;\n }\n\n // A sharded run's blob reports are merged, and uploaded by the Ralph\n // reporter, in `playwright merge-reports`.\n const reported =\n process.env[REPORTER_ENV] ||\n testInfo.config.reporter.some(([name]) => name === 'blob');\n if (mode === 'upload' && !reported) {\n throw new Error(\n \"Ralph uploads one merged graph per run from its reporter. Add ['@ralphralphai/playwright/reporter'] to `reporter` in playwright.config, or use the blob reporter and merge-reports when sharding.\",\n );\n }\n\n const root = testInfo.config.configFile\n ? path.dirname(testInfo.config.configFile)\n : process.cwd();\n\n const key = JSON.stringify({\n root,\n config: ralphOptions.config,\n configPath: ralphOptions.configPath,\n });\n\n let pending = _ralphConfigs.get(key);\n if (!pending) {\n pending = loadConfig(ralphOptions, root);\n _ralphConfigs.set(key, pending);\n }\n\n const recorder = new Recorder(\n ralphOptions,\n await pending,\n testInfo,\n mode,\n );\n\n try {\n await use(recorder);\n } finally {\n await recorder.finish();\n }\n },\n { auto: true, timeout: 30_000 },\n ],\n ralph: async ({ _ralphRecorder, ralphOptions }, use) => {\n const checkOptIn = () => {\n if (\n resolveMode(ralphOptions) !== 'off' &&\n !ralphOptions.ignoreUntaggedCalls\n ) {\n throw new Error('Opt this test into Ralph with the @ralph tag.');\n }\n };\n\n await use(\n _ralphRecorder ?? {\n recordNode: async () => {\n checkOptIn();\n },\n setNodeConditions: async () => {\n checkOptIn();\n return async () => {};\n },\n waitForCapture: async () => {},\n pause: () => {},\n resume: () => {},\n observe: () => {\n checkOptIn();\n },\n },\n );\n },\n // On the context rather than per page, so popups start out reduced too.\n contextOptions: async ({ contextOptions, ralphOptions }, use, testInfo) => {\n const recording =\n testInfo.tags.includes('@ralph') && resolveMode(ralphOptions) !== 'off';\n await use(\n recording && ralphOptions.reduceMotion\n ? { ...contextOptions, reducedMotion: 'reduce' }\n : contextOptions,\n );\n },\n context: async ({ context, _ralphRecorder }, use) => {\n _ralphRecorder?.observe(context);\n try {\n await use(context);\n } finally {\n // The base context fixture closes its pages next; pending recordings still need them alive.\n await _ralphRecorder?.stopContext(context);\n }\n },\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["loadConfigSync","base","resolveMode","path","loadConfig","Recorder"],"sources":["../src/screens.ts","../src/index.ts"],"sourcesContent":["import type {\n PlaywrightTestOptions,\n PlaywrightWorkerOptions,\n Project,\n TestInfo,\n} from '@playwright/test';\nimport type { ScreenSize } from '@ralphralphai/config';\n\nimport { loadConfigSync } from './config';\nimport type { RalphOptions } from './types';\n\ntype RalphProject = Project<\n PlaywrightTestOptions & { ralphOptions: RalphOptions },\n PlaywrightWorkerOptions\n>;\n\nexport const SCREEN_TAG_PREFIX = '@ralph-screen:';\n\nexport type RalphProjectOptions = Pick<\n RalphOptions,\n 'config' | 'configPath'\n> & {\n /** Resolves a relative `configPath`; defaults to the current directory. */\n root?: string;\n};\n\ntype ScreenMetadata = { screenSize: ScreenSize; screenSizes: ScreenSize[] };\n\nexport function screenLabel(size: ScreenSize): string {\n return size.width + 'x' + size.height;\n}\n\n/**\n * Expands `base` into one project per `screenSizes` entry of the Ralph config.\n * Each project's viewport is that screen size.\n */\nexport function ralphProjects(\n base: RalphProject = {},\n options: RalphProjectOptions = {},\n): RalphProject[] {\n const { screenSizes } = loadConfigSync(\n options,\n options.root ?? process.cwd(),\n ).config;\n if (!screenSizes.length) {\n throw new Error('The Ralph config declares no screenSizes.');\n }\n return screenSizes.map((screenSize) => {\n const label = screenLabel(screenSize);\n const ralph: ScreenMetadata = { screenSize, screenSizes };\n return {\n ...base,\n name: base.name ? base.name + ' ' + label : label,\n use: {\n ...base.use,\n viewport: { width: screenSize.width, height: screenSize.height },\n },\n metadata: { ...base.metadata, ralph },\n };\n });\n}\n\n/**\n * Skips a test whose `@ralph-screen:WxH` tags exclude the current project's\n * screen size. Untagged tests run at every size; other projects are untouched.\n */\nexport function applyScreenSelection(testInfo: TestInfo): void {\n const selected = testInfo.tags\n .filter((tag) => tag.startsWith(SCREEN_TAG_PREFIX))\n .map((tag) => tag.slice(SCREEN_TAG_PREFIX.length));\n const metadata = (testInfo.project.metadata as { ralph?: ScreenMetadata })\n .ralph;\n if (!selected.length || !metadata) {\n return;\n }\n const known = metadata.screenSizes.map(screenLabel);\n const unknown = selected.filter((label) => !known.includes(label));\n if (unknown.length) {\n throw new Error(\n 'Unknown Ralph screen size ' +\n unknown.join(', ') +\n '; the config declares ' +\n known.join(', ') +\n '.',\n );\n }\n const current = screenLabel(metadata.screenSize);\n testInfo.skip(\n !selected.includes(current),\n 'Not selected for Ralph screen size ' + current + '.',\n );\n}\n","import path from 'node:path';\n\nimport { test as base } from '@playwright/test';\nimport type {\n PlaywrightTestArgs,\n PlaywrightTestOptions,\n PlaywrightWorkerArgs,\n PlaywrightWorkerOptions,\n TestType,\n} from '@playwright/test';\n\nimport { loadConfig, resolveMode } from './config';\nimport { Recorder, REPORTER_ENV } from './recorder';\nimport { applyScreenSelection } from './screens';\nimport type { ConfigSnapshot, Ralph, RalphOptions } from './types';\n\nexport { expect, mergeTests } from '@playwright/test';\nexport type { WebRalphConfig } from '@ralphralphai/config';\nexport {\n createRawGraphBundle,\n prepareArtifactUpload,\n uploadRawGraph,\n} from './upload';\nexport type {\n PreparedUpload,\n UploadOptions,\n UploadReceipt,\n RawGraphScreenshot,\n RawGraphBundleIndex,\n} from './upload';\nexport { MANIFEST_ATTACHMENT } from './recorder';\nexport { ralphProjects, SCREEN_TAG_PREFIX } from './screens';\nexport type { RalphProjectOptions } from './screens';\nexport type { RalphReporterOptions } from './reporter';\nexport type {\n RawGraphManifest,\n RawGraph,\n RawGraphFile,\n RawPage,\n RawNode,\n RawAction,\n ActionKind,\n ActionTarget,\n ConditionsDeclaration,\n NodeConditions,\n RecordedTags,\n RecordNodeOptions,\n TagConditions,\n TagDeclaration,\n TagValue,\n ConfigSnapshot,\n PageLayout,\n Ralph,\n RalphOptions,\n TrackedElement,\n} from './types';\n\nexport type RalphFixtures = { ralph: Ralph; ralphOptions: RalphOptions };\n\ntype InternalFixtures = {\n _ralphScreen: void;\n _ralphRecorder: Recorder | null;\n};\ntype WorkerFixtures = { _ralphConfigs: Map<string, Promise<ConfigSnapshot>> };\n\nexport const test: TestType<\n PlaywrightTestArgs & PlaywrightTestOptions & RalphFixtures,\n PlaywrightWorkerArgs & PlaywrightWorkerOptions\n> = base.extend<RalphFixtures & InternalFixtures, WorkerFixtures>({\n ralphOptions: [{}, { option: true }],\n _ralphConfigs: [\n // Playwright requires destructuring here to discover fixture dependencies.\n // oxlint-disable-next-line no-empty-pattern\n async ({}, use) => {\n await use(new Map());\n },\n { scope: 'worker' },\n ],\n _ralphScreen: [\n // Playwright requires destructuring here to discover fixture dependencies.\n // oxlint-disable-next-line no-empty-pattern\n async ({}, use, testInfo) => {\n // Screen selection chooses which flows run, so it applies in every mode.\n applyScreenSelection(testInfo);\n await use();\n },\n { auto: true },\n ],\n _ralphRecorder: [\n // _ralphScreen is specified here so that the playwright treat that it is\n // a dependency of this fixture.\n async (\n { ralphOptions, _ralphConfigs, _ralphScreen, playwright },\n use,\n testInfo,\n ) => {\n const mode = resolveMode(ralphOptions);\n if (!testInfo.tags.includes('@ralph') || mode === 'off') {\n await use(null);\n return;\n }\n\n // A sharded run's blob reports are merged, and uploaded by the Ralph\n // reporter, in `playwright merge-reports`.\n const reported =\n process.env[REPORTER_ENV] ||\n testInfo.config.reporter.some(([name]) => name === 'blob');\n if (mode === 'upload' && !reported) {\n throw new Error(\n \"Ralph uploads one merged graph per run from its reporter. Add ['@ralphralphai/playwright/reporter'] to `reporter` in playwright.config, or use the blob reporter and merge-reports when sharding.\",\n );\n }\n\n const root = testInfo.config.configFile\n ? path.dirname(testInfo.config.configFile)\n : process.cwd();\n\n const key = JSON.stringify({\n root,\n config: ralphOptions.config,\n configPath: ralphOptions.configPath,\n });\n\n let pending = _ralphConfigs.get(key);\n if (!pending) {\n pending = loadConfig(ralphOptions, root);\n _ralphConfigs.set(key, pending);\n }\n\n const recorder = new Recorder(\n ralphOptions,\n await pending,\n testInfo,\n mode,\n playwright,\n );\n\n try {\n await use(recorder);\n } finally {\n await recorder.finish();\n }\n },\n { auto: true, timeout: 30_000 },\n ],\n ralph: async ({ _ralphRecorder, ralphOptions }, use) => {\n const checkOptIn = () => {\n if (\n resolveMode(ralphOptions) !== 'off' &&\n !ralphOptions.ignoreUntaggedCalls\n ) {\n throw new Error('Opt this test into Ralph with the @ralph tag.');\n }\n };\n\n await use(\n _ralphRecorder ?? {\n recordNode: async () => {\n checkOptIn();\n },\n setNodeConditions: async () => {\n checkOptIn();\n return async () => {};\n },\n waitForCapture: async () => {},\n pause: () => {},\n resume: () => {},\n observe: () => {\n checkOptIn();\n },\n },\n );\n },\n // On the context rather than per page, so popups start out reduced too.\n contextOptions: async ({ contextOptions, ralphOptions }, use, testInfo) => {\n const recording =\n testInfo.tags.includes('@ralph') && resolveMode(ralphOptions) !== 'off';\n await use(\n recording && ralphOptions.reduceMotion\n ? { ...contextOptions, reducedMotion: 'reduce' }\n : contextOptions,\n );\n },\n context: async ({ context, _ralphRecorder }, use) => {\n _ralphRecorder?.observe(context);\n try {\n await use(context);\n } finally {\n // The base context fixture closes its pages next; pending recordings still need them alive.\n await _ralphRecorder?.stopContext(context);\n }\n },\n});\n"],"mappings":"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"}
|
package/dist/index.d.mts
CHANGED
|
@@ -49,6 +49,12 @@ type RecordNodeOptions = {
|
|
|
49
49
|
* graph. A retake without one keeps the name already given.
|
|
50
50
|
*/
|
|
51
51
|
name?: string;
|
|
52
|
+
/**
|
|
53
|
+
* How the test got here from the node before, such as `Add to cart`, shown
|
|
54
|
+
* on the edge into this node beside the actions recorded on it. A retake
|
|
55
|
+
* without one keeps the label already given.
|
|
56
|
+
*/
|
|
57
|
+
edgeLabel?: string;
|
|
52
58
|
};
|
|
53
59
|
type Ralph = {
|
|
54
60
|
/**
|
|
@@ -76,6 +82,39 @@ type TrackedElement = {
|
|
|
76
82
|
width: number;
|
|
77
83
|
height: number;
|
|
78
84
|
};
|
|
85
|
+
type ActionKind = 'click' | 'dblclick' | 'tap' | 'hover' | 'check' | 'uncheck' | 'fill' | 'type' | 'press' | 'selectOption' | 'setInputFiles' | 'dragTo' | 'goto' | 'goBack' | 'goForward' | 'reload';
|
|
86
|
+
/** The element an action landed on, as the page showed it at that moment. */
|
|
87
|
+
type ActionTarget = {
|
|
88
|
+
tag: string;
|
|
89
|
+
role?: string;
|
|
90
|
+
/** What a reader would call it: its label, its text, or its alt text. */
|
|
91
|
+
name?: string;
|
|
92
|
+
trackId?: string;
|
|
93
|
+
/** In CSS pixels from the document's top-left, the space of the screenshot of the node the action was taken on. */
|
|
94
|
+
box: {
|
|
95
|
+
x: number;
|
|
96
|
+
y: number;
|
|
97
|
+
width: number;
|
|
98
|
+
height: number;
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
type RawAction = {
|
|
102
|
+
kind: ActionKind;
|
|
103
|
+
startedAt: string;
|
|
104
|
+
/** The tab it happened in, known when it reached an element. */
|
|
105
|
+
pageId?: string;
|
|
106
|
+
/** What the test acted on, in Playwright's selector syntax. */
|
|
107
|
+
selector?: string;
|
|
108
|
+
/**
|
|
109
|
+
* The text, key, URL, option, or file names, or for `dragTo`, the
|
|
110
|
+
* selector it was dropped on. Left out for what was typed
|
|
111
|
+
* into a password or one-time-code field, or into a field the page did not
|
|
112
|
+
* report.
|
|
113
|
+
*/
|
|
114
|
+
value?: string;
|
|
115
|
+
/** Absent when the page reported no element for it, e.g. `goto`. */
|
|
116
|
+
target?: ActionTarget;
|
|
117
|
+
};
|
|
79
118
|
type PageLayout = {
|
|
80
119
|
viewport: {
|
|
81
120
|
width: number;
|
|
@@ -113,6 +152,13 @@ type RawNode = {
|
|
|
113
152
|
* failed nodes are included; the server walks past the ones it doesn't show.
|
|
114
153
|
*/
|
|
115
154
|
previousNodeId?: string;
|
|
155
|
+
/** The test's words for how it got here from `previousNodeId`. */
|
|
156
|
+
edgeLabel?: string;
|
|
157
|
+
/**
|
|
158
|
+
* What the test did between requesting `previousNodeId` and this node, in
|
|
159
|
+
* order. Actions taken while recording was paused are left out.
|
|
160
|
+
*/
|
|
161
|
+
actions?: RawAction[];
|
|
116
162
|
} & ({
|
|
117
163
|
status: 'recorded';
|
|
118
164
|
recordedAt: string;
|
|
@@ -287,5 +333,5 @@ type RalphFixtures = {
|
|
|
287
333
|
};
|
|
288
334
|
declare const test: TestType<PlaywrightTestArgs & PlaywrightTestOptions & RalphFixtures, PlaywrightWorkerArgs & PlaywrightWorkerOptions>;
|
|
289
335
|
//#endregion
|
|
290
|
-
export { type ConditionsDeclaration, type ConfigSnapshot, MANIFEST_ATTACHMENT, type NodeConditions, type PageLayout, type PreparedUpload, type Ralph, RalphFixtures, type RalphOptions, type RalphProjectOptions, type RalphReporterOptions, type RawGraph, type RawGraphBundleIndex, type RawGraphFile, type RawGraphManifest, type RawGraphScreenshot, type RawNode, type RawPage, type RecordNodeOptions, type RecordedTags, SCREEN_TAG_PREFIX, type TagConditions, type TagDeclaration, type TagValue, type TrackedElement, type UploadOptions, type UploadReceipt, type WebRalphConfig, createRawGraphBundle, expect, mergeTests, prepareArtifactUpload, ralphProjects, test, uploadRawGraph };
|
|
336
|
+
export { type ActionKind, type ActionTarget, type ConditionsDeclaration, type ConfigSnapshot, MANIFEST_ATTACHMENT, type NodeConditions, type PageLayout, type PreparedUpload, type Ralph, RalphFixtures, type RalphOptions, type RalphProjectOptions, type RalphReporterOptions, type RawAction, type RawGraph, type RawGraphBundleIndex, type RawGraphFile, type RawGraphManifest, type RawGraphScreenshot, type RawNode, type RawPage, type RecordNodeOptions, type RecordedTags, SCREEN_TAG_PREFIX, type TagConditions, type TagDeclaration, type TagValue, type TrackedElement, type UploadOptions, type UploadReceipt, type WebRalphConfig, createRawGraphBundle, expect, mergeTests, prepareArtifactUpload, ralphProjects, test, uploadRawGraph };
|
|
291
337
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c as MANIFEST_ATTACHMENT, f as loadConfig, i as prepareArtifactUpload, m as resolveMode, p as loadConfigSync, r as createRawGraphBundle, s as uploadRawGraph, u as Recorder } from "./upload-
|
|
1
|
+
import { c as MANIFEST_ATTACHMENT, f as loadConfig, i as prepareArtifactUpload, m as resolveMode, p as loadConfigSync, r as createRawGraphBundle, s as uploadRawGraph, u as Recorder } from "./upload-D5hePvmA.mjs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { expect, mergeTests, test as test$1 } from "@playwright/test";
|
|
4
4
|
//#region src/screens.ts
|
|
@@ -61,7 +61,7 @@ const test = test$1.extend({
|
|
|
61
61
|
applyScreenSelection(testInfo);
|
|
62
62
|
await use();
|
|
63
63
|
}, { auto: true }],
|
|
64
|
-
_ralphRecorder: [async ({ ralphOptions, _ralphConfigs, _ralphScreen }, use, testInfo) => {
|
|
64
|
+
_ralphRecorder: [async ({ ralphOptions, _ralphConfigs, _ralphScreen, playwright }, use, testInfo) => {
|
|
65
65
|
const mode = resolveMode(ralphOptions);
|
|
66
66
|
if (!testInfo.tags.includes("@ralph") || mode === "off") {
|
|
67
67
|
await use(null);
|
|
@@ -80,7 +80,7 @@ const test = test$1.extend({
|
|
|
80
80
|
pending = loadConfig(ralphOptions, root);
|
|
81
81
|
_ralphConfigs.set(key, pending);
|
|
82
82
|
}
|
|
83
|
-
const recorder = new Recorder(ralphOptions, await pending, testInfo, mode);
|
|
83
|
+
const recorder = new Recorder(ralphOptions, await pending, testInfo, mode, playwright);
|
|
84
84
|
try {
|
|
85
85
|
await use(recorder);
|
|
86
86
|
} finally {
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["base"],"sources":["../src/screens.ts","../src/index.ts"],"sourcesContent":["import type {\n PlaywrightTestOptions,\n PlaywrightWorkerOptions,\n Project,\n TestInfo,\n} from '@playwright/test';\nimport type { ScreenSize } from '@ralphralphai/config';\n\nimport { loadConfigSync } from './config';\nimport type { RalphOptions } from './types';\n\ntype RalphProject = Project<\n PlaywrightTestOptions & { ralphOptions: RalphOptions },\n PlaywrightWorkerOptions\n>;\n\nexport const SCREEN_TAG_PREFIX = '@ralph-screen:';\n\nexport type RalphProjectOptions = Pick<\n RalphOptions,\n 'config' | 'configPath'\n> & {\n /** Resolves a relative `configPath`; defaults to the current directory. */\n root?: string;\n};\n\ntype ScreenMetadata = { screenSize: ScreenSize; screenSizes: ScreenSize[] };\n\nexport function screenLabel(size: ScreenSize): string {\n return size.width + 'x' + size.height;\n}\n\n/**\n * Expands `base` into one project per `screenSizes` entry of the Ralph config.\n * Each project's viewport is that screen size.\n */\nexport function ralphProjects(\n base: RalphProject = {},\n options: RalphProjectOptions = {},\n): RalphProject[] {\n const { screenSizes } = loadConfigSync(\n options,\n options.root ?? process.cwd(),\n ).config;\n if (!screenSizes.length) {\n throw new Error('The Ralph config declares no screenSizes.');\n }\n return screenSizes.map((screenSize) => {\n const label = screenLabel(screenSize);\n const ralph: ScreenMetadata = { screenSize, screenSizes };\n return {\n ...base,\n name: base.name ? base.name + ' ' + label : label,\n use: {\n ...base.use,\n viewport: { width: screenSize.width, height: screenSize.height },\n },\n metadata: { ...base.metadata, ralph },\n };\n });\n}\n\n/**\n * Skips a test whose `@ralph-screen:WxH` tags exclude the current project's\n * screen size. Untagged tests run at every size; other projects are untouched.\n */\nexport function applyScreenSelection(testInfo: TestInfo): void {\n const selected = testInfo.tags\n .filter((tag) => tag.startsWith(SCREEN_TAG_PREFIX))\n .map((tag) => tag.slice(SCREEN_TAG_PREFIX.length));\n const metadata = (testInfo.project.metadata as { ralph?: ScreenMetadata })\n .ralph;\n if (!selected.length || !metadata) {\n return;\n }\n const known = metadata.screenSizes.map(screenLabel);\n const unknown = selected.filter((label) => !known.includes(label));\n if (unknown.length) {\n throw new Error(\n 'Unknown Ralph screen size ' +\n unknown.join(', ') +\n '; the config declares ' +\n known.join(', ') +\n '.',\n );\n }\n const current = screenLabel(metadata.screenSize);\n testInfo.skip(\n !selected.includes(current),\n 'Not selected for Ralph screen size ' + current + '.',\n );\n}\n","import path from 'node:path';\n\nimport { test as base } from '@playwright/test';\nimport type {\n PlaywrightTestArgs,\n PlaywrightTestOptions,\n PlaywrightWorkerArgs,\n PlaywrightWorkerOptions,\n TestType,\n} from '@playwright/test';\n\nimport { loadConfig, resolveMode } from './config';\nimport { Recorder, REPORTER_ENV } from './recorder';\nimport { applyScreenSelection } from './screens';\nimport type { ConfigSnapshot, Ralph, RalphOptions } from './types';\n\nexport { expect, mergeTests } from '@playwright/test';\nexport type { WebRalphConfig } from '@ralphralphai/config';\nexport {\n createRawGraphBundle,\n prepareArtifactUpload,\n uploadRawGraph,\n} from './upload';\nexport type {\n PreparedUpload,\n UploadOptions,\n UploadReceipt,\n RawGraphScreenshot,\n RawGraphBundleIndex,\n} from './upload';\nexport { MANIFEST_ATTACHMENT } from './recorder';\nexport { ralphProjects, SCREEN_TAG_PREFIX } from './screens';\nexport type { RalphProjectOptions } from './screens';\nexport type { RalphReporterOptions } from './reporter';\nexport type {\n RawGraphManifest,\n RawGraph,\n RawGraphFile,\n RawPage,\n RawNode,\n ConditionsDeclaration,\n NodeConditions,\n RecordedTags,\n RecordNodeOptions,\n TagConditions,\n TagDeclaration,\n TagValue,\n ConfigSnapshot,\n PageLayout,\n Ralph,\n RalphOptions,\n TrackedElement,\n} from './types';\n\nexport type RalphFixtures = { ralph: Ralph; ralphOptions: RalphOptions };\n\ntype InternalFixtures = {\n _ralphScreen: void;\n _ralphRecorder: Recorder | null;\n};\ntype WorkerFixtures = { _ralphConfigs: Map<string, Promise<ConfigSnapshot>> };\n\nexport const test: TestType<\n PlaywrightTestArgs & PlaywrightTestOptions & RalphFixtures,\n PlaywrightWorkerArgs & PlaywrightWorkerOptions\n> = base.extend<RalphFixtures & InternalFixtures, WorkerFixtures>({\n ralphOptions: [{}, { option: true }],\n _ralphConfigs: [\n // Playwright requires destructuring here to discover fixture dependencies.\n // oxlint-disable-next-line no-empty-pattern\n async ({}, use) => {\n await use(new Map());\n },\n { scope: 'worker' },\n ],\n _ralphScreen: [\n // Playwright requires destructuring here to discover fixture dependencies.\n // oxlint-disable-next-line no-empty-pattern\n async ({}, use, testInfo) => {\n // Screen selection chooses which flows run, so it applies in every mode.\n applyScreenSelection(testInfo);\n await use();\n },\n { auto: true },\n ],\n _ralphRecorder: [\n // _ralphScreen is specified here so that the playwright treat that it is\n // a dependency of this fixture.\n async ({ ralphOptions, _ralphConfigs, _ralphScreen }, use, testInfo) => {\n const mode = resolveMode(ralphOptions);\n if (!testInfo.tags.includes('@ralph') || mode === 'off') {\n await use(null);\n return;\n }\n\n // A sharded run's blob reports are merged, and uploaded by the Ralph\n // reporter, in `playwright merge-reports`.\n const reported =\n process.env[REPORTER_ENV] ||\n testInfo.config.reporter.some(([name]) => name === 'blob');\n if (mode === 'upload' && !reported) {\n throw new Error(\n \"Ralph uploads one merged graph per run from its reporter. Add ['@ralphralphai/playwright/reporter'] to `reporter` in playwright.config, or use the blob reporter and merge-reports when sharding.\",\n );\n }\n\n const root = testInfo.config.configFile\n ? path.dirname(testInfo.config.configFile)\n : process.cwd();\n\n const key = JSON.stringify({\n root,\n config: ralphOptions.config,\n configPath: ralphOptions.configPath,\n });\n\n let pending = _ralphConfigs.get(key);\n if (!pending) {\n pending = loadConfig(ralphOptions, root);\n _ralphConfigs.set(key, pending);\n }\n\n const recorder = new Recorder(\n ralphOptions,\n await pending,\n testInfo,\n mode,\n );\n\n try {\n await use(recorder);\n } finally {\n await recorder.finish();\n }\n },\n { auto: true, timeout: 30_000 },\n ],\n ralph: async ({ _ralphRecorder, ralphOptions }, use) => {\n const checkOptIn = () => {\n if (\n resolveMode(ralphOptions) !== 'off' &&\n !ralphOptions.ignoreUntaggedCalls\n ) {\n throw new Error('Opt this test into Ralph with the @ralph tag.');\n }\n };\n\n await use(\n _ralphRecorder ?? {\n recordNode: async () => {\n checkOptIn();\n },\n setNodeConditions: async () => {\n checkOptIn();\n return async () => {};\n },\n waitForCapture: async () => {},\n pause: () => {},\n resume: () => {},\n observe: () => {\n checkOptIn();\n },\n },\n );\n },\n // On the context rather than per page, so popups start out reduced too.\n contextOptions: async ({ contextOptions, ralphOptions }, use, testInfo) => {\n const recording =\n testInfo.tags.includes('@ralph') && resolveMode(ralphOptions) !== 'off';\n await use(\n recording && ralphOptions.reduceMotion\n ? { ...contextOptions, reducedMotion: 'reduce' }\n : contextOptions,\n );\n },\n context: async ({ context, _ralphRecorder }, use) => {\n _ralphRecorder?.observe(context);\n try {\n await use(context);\n } finally {\n // The base context fixture closes its pages next; pending recordings still need them alive.\n await _ralphRecorder?.stopContext(context);\n }\n },\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["base"],"sources":["../src/screens.ts","../src/index.ts"],"sourcesContent":["import type {\n PlaywrightTestOptions,\n PlaywrightWorkerOptions,\n Project,\n TestInfo,\n} from '@playwright/test';\nimport type { ScreenSize } from '@ralphralphai/config';\n\nimport { loadConfigSync } from './config';\nimport type { RalphOptions } from './types';\n\ntype RalphProject = Project<\n PlaywrightTestOptions & { ralphOptions: RalphOptions },\n PlaywrightWorkerOptions\n>;\n\nexport const SCREEN_TAG_PREFIX = '@ralph-screen:';\n\nexport type RalphProjectOptions = Pick<\n RalphOptions,\n 'config' | 'configPath'\n> & {\n /** Resolves a relative `configPath`; defaults to the current directory. */\n root?: string;\n};\n\ntype ScreenMetadata = { screenSize: ScreenSize; screenSizes: ScreenSize[] };\n\nexport function screenLabel(size: ScreenSize): string {\n return size.width + 'x' + size.height;\n}\n\n/**\n * Expands `base` into one project per `screenSizes` entry of the Ralph config.\n * Each project's viewport is that screen size.\n */\nexport function ralphProjects(\n base: RalphProject = {},\n options: RalphProjectOptions = {},\n): RalphProject[] {\n const { screenSizes } = loadConfigSync(\n options,\n options.root ?? process.cwd(),\n ).config;\n if (!screenSizes.length) {\n throw new Error('The Ralph config declares no screenSizes.');\n }\n return screenSizes.map((screenSize) => {\n const label = screenLabel(screenSize);\n const ralph: ScreenMetadata = { screenSize, screenSizes };\n return {\n ...base,\n name: base.name ? base.name + ' ' + label : label,\n use: {\n ...base.use,\n viewport: { width: screenSize.width, height: screenSize.height },\n },\n metadata: { ...base.metadata, ralph },\n };\n });\n}\n\n/**\n * Skips a test whose `@ralph-screen:WxH` tags exclude the current project's\n * screen size. Untagged tests run at every size; other projects are untouched.\n */\nexport function applyScreenSelection(testInfo: TestInfo): void {\n const selected = testInfo.tags\n .filter((tag) => tag.startsWith(SCREEN_TAG_PREFIX))\n .map((tag) => tag.slice(SCREEN_TAG_PREFIX.length));\n const metadata = (testInfo.project.metadata as { ralph?: ScreenMetadata })\n .ralph;\n if (!selected.length || !metadata) {\n return;\n }\n const known = metadata.screenSizes.map(screenLabel);\n const unknown = selected.filter((label) => !known.includes(label));\n if (unknown.length) {\n throw new Error(\n 'Unknown Ralph screen size ' +\n unknown.join(', ') +\n '; the config declares ' +\n known.join(', ') +\n '.',\n );\n }\n const current = screenLabel(metadata.screenSize);\n testInfo.skip(\n !selected.includes(current),\n 'Not selected for Ralph screen size ' + current + '.',\n );\n}\n","import path from 'node:path';\n\nimport { test as base } from '@playwright/test';\nimport type {\n PlaywrightTestArgs,\n PlaywrightTestOptions,\n PlaywrightWorkerArgs,\n PlaywrightWorkerOptions,\n TestType,\n} from '@playwright/test';\n\nimport { loadConfig, resolveMode } from './config';\nimport { Recorder, REPORTER_ENV } from './recorder';\nimport { applyScreenSelection } from './screens';\nimport type { ConfigSnapshot, Ralph, RalphOptions } from './types';\n\nexport { expect, mergeTests } from '@playwright/test';\nexport type { WebRalphConfig } from '@ralphralphai/config';\nexport {\n createRawGraphBundle,\n prepareArtifactUpload,\n uploadRawGraph,\n} from './upload';\nexport type {\n PreparedUpload,\n UploadOptions,\n UploadReceipt,\n RawGraphScreenshot,\n RawGraphBundleIndex,\n} from './upload';\nexport { MANIFEST_ATTACHMENT } from './recorder';\nexport { ralphProjects, SCREEN_TAG_PREFIX } from './screens';\nexport type { RalphProjectOptions } from './screens';\nexport type { RalphReporterOptions } from './reporter';\nexport type {\n RawGraphManifest,\n RawGraph,\n RawGraphFile,\n RawPage,\n RawNode,\n RawAction,\n ActionKind,\n ActionTarget,\n ConditionsDeclaration,\n NodeConditions,\n RecordedTags,\n RecordNodeOptions,\n TagConditions,\n TagDeclaration,\n TagValue,\n ConfigSnapshot,\n PageLayout,\n Ralph,\n RalphOptions,\n TrackedElement,\n} from './types';\n\nexport type RalphFixtures = { ralph: Ralph; ralphOptions: RalphOptions };\n\ntype InternalFixtures = {\n _ralphScreen: void;\n _ralphRecorder: Recorder | null;\n};\ntype WorkerFixtures = { _ralphConfigs: Map<string, Promise<ConfigSnapshot>> };\n\nexport const test: TestType<\n PlaywrightTestArgs & PlaywrightTestOptions & RalphFixtures,\n PlaywrightWorkerArgs & PlaywrightWorkerOptions\n> = base.extend<RalphFixtures & InternalFixtures, WorkerFixtures>({\n ralphOptions: [{}, { option: true }],\n _ralphConfigs: [\n // Playwright requires destructuring here to discover fixture dependencies.\n // oxlint-disable-next-line no-empty-pattern\n async ({}, use) => {\n await use(new Map());\n },\n { scope: 'worker' },\n ],\n _ralphScreen: [\n // Playwright requires destructuring here to discover fixture dependencies.\n // oxlint-disable-next-line no-empty-pattern\n async ({}, use, testInfo) => {\n // Screen selection chooses which flows run, so it applies in every mode.\n applyScreenSelection(testInfo);\n await use();\n },\n { auto: true },\n ],\n _ralphRecorder: [\n // _ralphScreen is specified here so that the playwright treat that it is\n // a dependency of this fixture.\n async (\n { ralphOptions, _ralphConfigs, _ralphScreen, playwright },\n use,\n testInfo,\n ) => {\n const mode = resolveMode(ralphOptions);\n if (!testInfo.tags.includes('@ralph') || mode === 'off') {\n await use(null);\n return;\n }\n\n // A sharded run's blob reports are merged, and uploaded by the Ralph\n // reporter, in `playwright merge-reports`.\n const reported =\n process.env[REPORTER_ENV] ||\n testInfo.config.reporter.some(([name]) => name === 'blob');\n if (mode === 'upload' && !reported) {\n throw new Error(\n \"Ralph uploads one merged graph per run from its reporter. Add ['@ralphralphai/playwright/reporter'] to `reporter` in playwright.config, or use the blob reporter and merge-reports when sharding.\",\n );\n }\n\n const root = testInfo.config.configFile\n ? path.dirname(testInfo.config.configFile)\n : process.cwd();\n\n const key = JSON.stringify({\n root,\n config: ralphOptions.config,\n configPath: ralphOptions.configPath,\n });\n\n let pending = _ralphConfigs.get(key);\n if (!pending) {\n pending = loadConfig(ralphOptions, root);\n _ralphConfigs.set(key, pending);\n }\n\n const recorder = new Recorder(\n ralphOptions,\n await pending,\n testInfo,\n mode,\n playwright,\n );\n\n try {\n await use(recorder);\n } finally {\n await recorder.finish();\n }\n },\n { auto: true, timeout: 30_000 },\n ],\n ralph: async ({ _ralphRecorder, ralphOptions }, use) => {\n const checkOptIn = () => {\n if (\n resolveMode(ralphOptions) !== 'off' &&\n !ralphOptions.ignoreUntaggedCalls\n ) {\n throw new Error('Opt this test into Ralph with the @ralph tag.');\n }\n };\n\n await use(\n _ralphRecorder ?? {\n recordNode: async () => {\n checkOptIn();\n },\n setNodeConditions: async () => {\n checkOptIn();\n return async () => {};\n },\n waitForCapture: async () => {},\n pause: () => {},\n resume: () => {},\n observe: () => {\n checkOptIn();\n },\n },\n );\n },\n // On the context rather than per page, so popups start out reduced too.\n contextOptions: async ({ contextOptions, ralphOptions }, use, testInfo) => {\n const recording =\n testInfo.tags.includes('@ralph') && resolveMode(ralphOptions) !== 'off';\n await use(\n recording && ralphOptions.reduceMotion\n ? { ...contextOptions, reducedMotion: 'reduce' }\n : contextOptions,\n );\n },\n context: async ({ context, _ralphRecorder }, use) => {\n _ralphRecorder?.observe(context);\n try {\n await use(context);\n } finally {\n // The base context fixture closes its pages next; pending recordings still need them alive.\n await _ralphRecorder?.stopContext(context);\n }\n },\n});\n"],"mappings":";;;;AAgBA,MAAa,oBAAoB;AAYjC,SAAgB,YAAY,MAA0B;CACpD,OAAO,KAAK,QAAQ,MAAM,KAAK;AACjC;;;;;AAMA,SAAgB,cACd,OAAqB,CAAC,GACtB,UAA+B,CAAC,GAChB;CAChB,MAAM,EAAE,gBAAgB,eACtB,SACA,QAAQ,QAAQ,QAAQ,IAAI,CAC9B,CAAC,CAAC;CACF,IAAI,CAAC,YAAY,QACf,MAAM,IAAI,MAAM,2CAA2C;CAE7D,OAAO,YAAY,KAAK,eAAe;EACrC,MAAM,QAAQ,YAAY,UAAU;EACpC,MAAM,QAAwB;GAAE;GAAY;EAAY;EACxD,OAAO;GACL,GAAG;GACH,MAAM,KAAK,OAAO,KAAK,OAAO,MAAM,QAAQ;GAC5C,KAAK;IACH,GAAG,KAAK;IACR,UAAU;KAAE,OAAO,WAAW;KAAO,QAAQ,WAAW;IAAO;GACjE;GACA,UAAU;IAAE,GAAG,KAAK;IAAU;GAAM;EACtC;CACF,CAAC;AACH;;;;;AAMA,SAAgB,qBAAqB,UAA0B;CAC7D,MAAM,WAAW,SAAS,KACvB,QAAQ,QAAQ,IAAI,WAAW,iBAAiB,CAAC,CAAC,CAClD,KAAK,QAAQ,IAAI,MAAM,EAAwB,CAAC;CACnD,MAAM,WAAY,SAAS,QAAQ,SAChC;CACH,IAAI,CAAC,SAAS,UAAU,CAAC,UACvB;CAEF,MAAM,QAAQ,SAAS,YAAY,IAAI,WAAW;CAClD,MAAM,UAAU,SAAS,QAAQ,UAAU,CAAC,MAAM,SAAS,KAAK,CAAC;CACjE,IAAI,QAAQ,QACV,MAAM,IAAI,MACR,+BACE,QAAQ,KAAK,IAAI,IACjB,2BACA,MAAM,KAAK,IAAI,IACf,GACJ;CAEF,MAAM,UAAU,YAAY,SAAS,UAAU;CAC/C,SAAS,KACP,CAAC,SAAS,SAAS,OAAO,GAC1B,wCAAwC,UAAU,GACpD;AACF;;;AC1BA,MAAa,OAGTA,OAAK,OAAyD;CAChE,cAAc,CAAC,CAAC,GAAG,EAAE,QAAQ,KAAK,CAAC;CACnC,eAAe,CAGb,OAAO,IAAI,QAAQ;EACjB,MAAM,oBAAI,IAAI,IAAI,CAAC;CACrB,GACA,EAAE,OAAO,SAAS,CACpB;CACA,cAAc,CAGZ,OAAO,IAAI,KAAK,aAAa;EAE3B,qBAAqB,QAAQ;EAC7B,MAAM,IAAI;CACZ,GACA,EAAE,MAAM,KAAK,CACf;CACA,gBAAgB,CAGd,OACE,EAAE,cAAc,eAAe,cAAc,cAC7C,KACA,aACG;EACH,MAAM,OAAO,YAAY,YAAY;EACrC,IAAI,CAAC,SAAS,KAAK,SAAS,QAAQ,KAAK,SAAS,OAAO;GACvD,MAAM,IAAI,IAAI;GACd;EACF;EAIA,MAAM,WACJ,QAAQ,IAAA,gCACR,SAAS,OAAO,SAAS,MAAM,CAAC,UAAU,SAAS,MAAM;EAC3D,IAAI,SAAS,YAAY,CAAC,UACxB,MAAM,IAAI,MACR,mMACF;EAGF,MAAM,OAAO,SAAS,OAAO,aACzB,KAAK,QAAQ,SAAS,OAAO,UAAU,IACvC,QAAQ,IAAI;EAEhB,MAAM,MAAM,KAAK,UAAU;GACzB;GACA,QAAQ,aAAa;GACrB,YAAY,aAAa;EAC3B,CAAC;EAED,IAAI,UAAU,cAAc,IAAI,GAAG;EACnC,IAAI,CAAC,SAAS;GACZ,UAAU,WAAW,cAAc,IAAI;GACvC,cAAc,IAAI,KAAK,OAAO;EAChC;EAEA,MAAM,WAAW,IAAI,SACnB,cACA,MAAM,SACN,UACA,MACA,UACF;EAEA,IAAI;GACF,MAAM,IAAI,QAAQ;EACpB,UAAU;GACR,MAAM,SAAS,OAAO;EACxB;CACF,GACA;EAAE,MAAM;EAAM,SAAS;CAAO,CAChC;CACA,OAAO,OAAO,EAAE,gBAAgB,gBAAgB,QAAQ;EACtD,MAAM,mBAAmB;GACvB,IACE,YAAY,YAAY,MAAM,SAC9B,CAAC,aAAa,qBAEd,MAAM,IAAI,MAAM,+CAA+C;EAEnE;EAEA,MAAM,IACJ,kBAAkB;GAChB,YAAY,YAAY;IACtB,WAAW;GACb;GACA,mBAAmB,YAAY;IAC7B,WAAW;IACX,OAAO,YAAY,CAAC;GACtB;GACA,gBAAgB,YAAY,CAAC;GAC7B,aAAa,CAAC;GACd,cAAc,CAAC;GACf,eAAe;IACb,WAAW;GACb;EACF,CACF;CACF;CAEA,gBAAgB,OAAO,EAAE,gBAAgB,gBAAgB,KAAK,aAAa;EAGzE,MAAM,IADJ,SAAS,KAAK,SAAS,QAAQ,KAAK,YAAY,YAAY,MAAM,SAErD,aAAa,eACtB;GAAE,GAAG;GAAgB,eAAe;EAAS,IAC7C,cACN;CACF;CACA,SAAS,OAAO,EAAE,SAAS,kBAAkB,QAAQ;EACnD,gBAAgB,QAAQ,OAAO;EAC/B,IAAI;GACF,MAAM,IAAI,OAAO;EACnB,UAAU;GAER,MAAM,gBAAgB,YAAY,OAAO;EAC3C;CACF;AACF,CAAC"}
|
package/dist/reporter.cjs
CHANGED
package/dist/reporter.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as resolveUploadOptions, c as MANIFEST_ATTACHMENT, d as version, i as prepareArtifactUpload, l as REPORTER_ENV, m as resolveMode, n as bundleImagePath, o as resultLink, s as uploadRawGraph, t as RAW_GRAPH_BUNDLE_INDEX } from "./upload-
|
|
1
|
+
import { a as resolveUploadOptions, c as MANIFEST_ATTACHMENT, d as version, i as prepareArtifactUpload, l as REPORTER_ENV, m as resolveMode, n as bundleImagePath, o as resultLink, s as uploadRawGraph, t as RAW_GRAPH_BUNDLE_INDEX } from "./upload-D5hePvmA.mjs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
4
4
|
import { randomUUID } from "node:crypto";
|