UNPKG

playwright-core

Version:

A high-level API to automate web browsers

336 lines (335 loc) • 13.3 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var actionRunner_exports = {}; __export(actionRunner_exports, { runAction: () => runAction, traceParamsForAction: () => traceParamsForAction }); module.exports = __toCommonJS(actionRunner_exports); var import_expectUtils = require("../utils/expectUtils"); var import_urlMatch = require("../../utils/isomorphic/urlMatch"); var import_stringUtils = require("../../utils/isomorphic/stringUtils"); var import_time = require("../../utils/isomorphic/time"); var import_crypto = require("../utils/crypto"); var import_ariaSnapshot = require("../../utils/isomorphic/ariaSnapshot"); var import_locatorGenerators = require("../../utils/isomorphic/locatorGenerators"); var import_utilsBundle = require("../../utilsBundle"); var import_errors = require("../errors"); async function runAction(progress, mode, page, action, secrets) { const parentMetadata = progress.metadata; const frame = page.mainFrame(); const callMetadata = callMetadataForAction(progress, frame, action, mode); callMetadata.log = parentMetadata.log; progress.metadata = callMetadata; await frame.instrumentation.onBeforeCall(frame, callMetadata, parentMetadata.id); let error; const result = await innerRunAction(progress, mode, page, action, secrets).catch((e) => error = e); callMetadata.endTime = (0, import_time.monotonicTime)(); callMetadata.error = error ? (0, import_errors.serializeError)(error) : void 0; callMetadata.result = error ? void 0 : result; await frame.instrumentation.onAfterCall(frame, callMetadata); if (error) throw error; return result; } async function innerRunAction(progress, mode, page, action, secrets) { const frame = page.mainFrame(); const commonOptions = { strict: true, noAutoWaiting: mode === "generate" }; switch (action.method) { case "navigate": await frame.goto(progress, action.url); break; case "click": await frame.click(progress, action.selector, { button: action.button, clickCount: action.clickCount, modifiers: action.modifiers, ...commonOptions }); break; case "drag": await frame.dragAndDrop(progress, action.sourceSelector, action.targetSelector, { ...commonOptions }); break; case "hover": await frame.hover(progress, action.selector, { modifiers: action.modifiers, ...commonOptions }); break; case "selectOption": await frame.selectOption(progress, action.selector, [], action.labels.map((a) => ({ label: a })), { ...commonOptions }); break; case "pressKey": await page.keyboard.press(progress, action.key); break; case "pressSequentially": { const secret = secrets?.find((s) => s.name === action.text)?.value ?? action.text; await frame.type(progress, action.selector, secret, { ...commonOptions }); if (action.submit) await page.keyboard.press(progress, "Enter"); break; } case "fill": { const secret = secrets?.find((s) => s.name === action.text)?.value ?? action.text; await frame.fill(progress, action.selector, secret, { ...commonOptions }); if (action.submit) await page.keyboard.press(progress, "Enter"); break; } case "setChecked": if (action.checked) await frame.check(progress, action.selector, { ...commonOptions }); else await frame.uncheck(progress, action.selector, { ...commonOptions }); break; case "expectVisible": { await runExpect(frame, progress, mode, action.selector, { expression: "to.be.visible", isNot: !!action.isNot }, "visible", "toBeVisible", ""); break; } case "expectValue": { if (action.type === "textbox" || action.type === "combobox" || action.type === "slider") { const expectedText = (0, import_expectUtils.serializeExpectedTextValues)([action.value]); await runExpect(frame, progress, mode, action.selector, { expression: "to.have.value", expectedText, isNot: !!action.isNot }, action.value, "toHaveValue", "expected"); } else if (action.type === "checkbox" || action.type === "radio") { const expectedValue = { checked: action.value === "true" }; await runExpect(frame, progress, mode, action.selector, { selector: action.selector, expression: "to.be.checked", expectedValue, isNot: !!action.isNot }, action.value ? "checked" : "unchecked", "toBeChecked", ""); } else { throw new Error(`Unsupported element type: ${action.type}`); } break; } case "expectAria": { const expectedValue = (0, import_ariaSnapshot.parseAriaSnapshotUnsafe)(import_utilsBundle.yaml, action.template); await runExpect(frame, progress, mode, "body", { expression: "to.match.aria", expectedValue, isNot: !!action.isNot }, "\n" + action.template, "toMatchAriaSnapshot", "expected"); break; } case "expectURL": { if (!action.regex && !action.value) throw new Error("Either url or regex must be provided"); if (action.regex && action.value) throw new Error("Only one of url or regex can be provided"); const expected = action.regex ? (0, import_stringUtils.parseRegex)(action.regex) : (0, import_urlMatch.constructURLBasedOnBaseURL)(page.browserContext._options.baseURL, action.value); const expectedText = (0, import_expectUtils.serializeExpectedTextValues)([expected]); await runExpect(frame, progress, mode, void 0, { expression: "to.have.url", expectedText, isNot: !!action.isNot }, expected, "toHaveURL", "expected"); break; } case "expectTitle": { const expectedText = (0, import_expectUtils.serializeExpectedTextValues)([action.value], { normalizeWhiteSpace: true }); await runExpect(frame, progress, mode, void 0, { expression: "to.have.title", expectedText, isNot: !!action.isNot }, action.value, "toHaveTitle", "expected"); break; } } } async function runExpect(frame, progress, mode, selector, options, expected, matcherName, expectation) { const result = await frame.expect(progress, selector, { ...options, // When generating, we want the expect to pass or fail immediately and give feedback to the model. noAutoWaiting: mode === "generate", timeoutForLogs: mode === "generate" ? void 0 : progress.timeout }); if (!result.matches === !options.isNot) { const received = matcherName === "toMatchAriaSnapshot" ? "\n" + result.received.raw : result.received; const expectedSuffix = typeof expected === "string" ? "" : " pattern"; const expectedDisplay = typeof expected === "string" ? expected : expected.toString(); throw new Error((0, import_expectUtils.formatMatcherMessage)(import_expectUtils.simpleMatcherUtils, { isNot: options.isNot, matcherName, expectation, locator: selector ? (0, import_locatorGenerators.asLocatorDescription)("javascript", selector) : void 0, timedOut: result.timedOut, timeout: mode === "generate" ? void 0 : progress.timeout, printedExpected: options.isNot ? `Expected${expectedSuffix}: not ${expectedDisplay}` : `Expected${expectedSuffix}: ${expectedDisplay}`, printedReceived: result.errorMessage ? "" : `Received: ${received}`, errorMessage: result.errorMessage // Note: we are not passing call log, because it will be automatically appended on the client side, // as a part of the agent.{perform,expect} call. })); } } function traceParamsForAction(progress, action, mode) { const timeout = progress.timeout; switch (action.method) { case "navigate": { const params = { url: action.url, timeout }; return { type: "Frame", method: "goto", params }; } case "click": { const params = { selector: action.selector, strict: true, modifiers: action.modifiers, button: action.button, clickCount: action.clickCount, timeout }; return { type: "Frame", method: "click", params }; } case "drag": { const params = { source: action.sourceSelector, target: action.targetSelector, timeout }; return { type: "Frame", method: "dragAndDrop", params }; } case "hover": { const params = { selector: action.selector, modifiers: action.modifiers, timeout }; return { type: "Frame", method: "hover", params }; } case "pressKey": { const params = { key: action.key }; return { type: "Page", method: "keyboardPress", params }; } case "pressSequentially": { const params = { selector: action.selector, text: action.text, timeout }; return { type: "Frame", method: "type", params }; } case "fill": { const params = { selector: action.selector, strict: true, value: action.text, timeout }; return { type: "Frame", method: "fill", params }; } case "setChecked": { if (action.checked) { const params = { selector: action.selector, strict: true, timeout }; return { type: "Frame", method: "check", params }; } else { const params = { selector: action.selector, strict: true, timeout }; return { type: "Frame", method: "uncheck", params }; } } case "selectOption": { const params = { selector: action.selector, strict: true, options: action.labels.map((label) => ({ label })), timeout }; return { type: "Frame", method: "selectOption", params }; } case "expectValue": { if (action.type === "textbox" || action.type === "combobox" || action.type === "slider") { const expectedText = (0, import_expectUtils.serializeExpectedTextValues)([action.value]); const params = { selector: action.selector, expression: "to.have.value", expectedText, isNot: !!action.isNot, timeout }; return { type: "Frame", method: "expect", title: "Expect Value", params }; } else if (action.type === "checkbox" || action.type === "radio") { const params = { selector: action.selector, expression: "to.be.checked", isNot: !!action.isNot, timeout }; return { type: "Frame", method: "expect", title: "Expect Checked", params }; } else { throw new Error(`Unsupported element type: ${action.type}`); } } case "expectVisible": { const params = { selector: action.selector, expression: "to.be.visible", isNot: !!action.isNot, timeout }; return { type: "Frame", method: "expect", title: "Expect Visible", params }; } case "expectAria": { const params = { selector: "body", expression: "to.match.snapshot", expectedText: [], isNot: !!action.isNot, timeout }; return { type: "Frame", method: "expect", title: "Expect Aria Snapshot", params }; } case "expectURL": { const expected = action.regex ? (0, import_stringUtils.parseRegex)(action.regex) : action.value; const expectedText = (0, import_expectUtils.serializeExpectedTextValues)([expected]); const params = { selector: void 0, expression: "to.have.url", expectedText, isNot: !!action.isNot, timeout }; return { type: "Frame", method: "expect", title: "Expect URL", params }; } case "expectTitle": { const expectedText = (0, import_expectUtils.serializeExpectedTextValues)([action.value], { normalizeWhiteSpace: true }); const params = { selector: void 0, expression: "to.have.title", expectedText, isNot: !!action.isNot, timeout }; return { type: "Frame", method: "expect", title: "Expect Title", params }; } } } function callMetadataForAction(progress, frame, action, mode) { const callMetadata = { id: `call@${(0, import_crypto.createGuid)()}`, objectId: frame.guid, pageId: frame._page.guid, frameId: frame.guid, startTime: (0, import_time.monotonicTime)(), endTime: 0, log: [], ...traceParamsForAction(progress, action, mode) }; return callMetadata; } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { runAction, traceParamsForAction });