UNPKG

playwright-fluent

Version:
1,321 lines 74.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.cast = exports.PlaywrightFluent = exports.defaultTracingOptions = exports.defaultAssertOptions = exports.userHomeDirectory = exports.userDownloadsDirectory = exports.uniqueFilename = exports.noWaitNoThrowOptions = exports.harHeadersToHttpHeaders = exports.getHarResponseFor = exports.getHarResponseContentAs = exports.getHarDataFrom = exports.defaultWaitUntilOptions = exports.sizeOf = exports.allKnownDevices = exports.validateMock = exports.mockPostWithEmptyResponseAndStatus = exports.mockGetWithUnauthorizedResponse = exports.mockGetWithJsonResponseDependingOnQueryString = exports.mockGetWithJsonResponse = exports.mockGetWithJavascriptResponse = exports.mockGetWithForbiddenResponse = exports.mockGetWithEmptyResponseAndStatus = exports.getOutdatedMocks = exports.getMissingMocks = exports.generateCodeForMissingMock = exports.defaultMocksOptions = void 0; const tslib_1 = require("tslib"); const action = tslib_1.__importStar(require("../actions")); const actions_1 = require("../actions"); const assertion = tslib_1.__importStar(require("../assertions")); const devices_1 = require("../devices"); const selector_api_1 = require("../selector-api"); const utils_1 = require("../utils"); // eslint-disable-next-line @typescript-eslint/no-var-requires const isCI = require('is-ci'); var actions_2 = require("../actions"); Object.defineProperty(exports, "defaultMocksOptions", { enumerable: true, get: function () { return actions_2.defaultMocksOptions; } }); Object.defineProperty(exports, "generateCodeForMissingMock", { enumerable: true, get: function () { return actions_2.generateCodeForMissingMock; } }); Object.defineProperty(exports, "getMissingMocks", { enumerable: true, get: function () { return actions_2.getMissingMocks; } }); Object.defineProperty(exports, "getOutdatedMocks", { enumerable: true, get: function () { return actions_2.getOutdatedMocks; } }); Object.defineProperty(exports, "mockGetWithEmptyResponseAndStatus", { enumerable: true, get: function () { return actions_2.mockGetWithEmptyResponseAndStatus; } }); Object.defineProperty(exports, "mockGetWithForbiddenResponse", { enumerable: true, get: function () { return actions_2.mockGetWithForbiddenResponse; } }); Object.defineProperty(exports, "mockGetWithJavascriptResponse", { enumerable: true, get: function () { return actions_2.mockGetWithJavascriptResponse; } }); Object.defineProperty(exports, "mockGetWithJsonResponse", { enumerable: true, get: function () { return actions_2.mockGetWithJsonResponse; } }); Object.defineProperty(exports, "mockGetWithJsonResponseDependingOnQueryString", { enumerable: true, get: function () { return actions_2.mockGetWithJsonResponseDependingOnQueryString; } }); Object.defineProperty(exports, "mockGetWithUnauthorizedResponse", { enumerable: true, get: function () { return actions_2.mockGetWithUnauthorizedResponse; } }); Object.defineProperty(exports, "mockPostWithEmptyResponseAndStatus", { enumerable: true, get: function () { return actions_2.mockPostWithEmptyResponseAndStatus; } }); Object.defineProperty(exports, "validateMock", { enumerable: true, get: function () { return actions_2.validateMock; } }); var devices_2 = require("../devices"); Object.defineProperty(exports, "allKnownDevices", { enumerable: true, get: function () { return devices_2.allKnownDevices; } }); Object.defineProperty(exports, "sizeOf", { enumerable: true, get: function () { return devices_2.sizeOf; } }); var utils_2 = require("../utils"); Object.defineProperty(exports, "defaultWaitUntilOptions", { enumerable: true, get: function () { return utils_2.defaultWaitUntilOptions; } }); Object.defineProperty(exports, "getHarDataFrom", { enumerable: true, get: function () { return utils_2.getHarDataFrom; } }); Object.defineProperty(exports, "getHarResponseContentAs", { enumerable: true, get: function () { return utils_2.getHarResponseContentAs; } }); Object.defineProperty(exports, "getHarResponseFor", { enumerable: true, get: function () { return utils_2.getHarResponseFor; } }); Object.defineProperty(exports, "harHeadersToHttpHeaders", { enumerable: true, get: function () { return utils_2.harHeadersToHttpHeaders; } }); Object.defineProperty(exports, "noWaitNoThrowOptions", { enumerable: true, get: function () { return utils_2.noWaitNoThrowOptions; } }); Object.defineProperty(exports, "uniqueFilename", { enumerable: true, get: function () { return utils_2.uniqueFilename; } }); Object.defineProperty(exports, "userDownloadsDirectory", { enumerable: true, get: function () { return utils_2.userDownloadsDirectory; } }); Object.defineProperty(exports, "userHomeDirectory", { enumerable: true, get: function () { return utils_2.userHomeDirectory; } }); exports.defaultAssertOptions = { stabilityInMilliseconds: 300, timeoutInMilliseconds: 30000, verbose: false, }; exports.defaultTracingOptions = { screenshots: true, snapshots: true, }; class PlaywrightFluent { async then(onfulfilled, onrejected) { // prettier-ignore return await this.executeActions() .then(onfulfilled) .catch(onrejected); } lastError() { return this._lastError; } async executeActions() { try { this._lastError = undefined; // eslint-disable-next-line no-constant-condition while (true) { if (this.actions.length === 0) { break; } const action = this.actions.shift(); action && (await action()); } } catch (error) { this._lastError = error; this.actions = []; throw error; } finally { this.actions = []; } } constructor(browser, pageOrFrame) { this._isDialogOpened = false; this._isDialogClosed = true; this._hasBeenRedirectedToAnotherTab = false; this._context = {}; this.actions = []; this.launchOptions = actions_1.defaultLaunchOptions; this.defaultWaitOptions = { ...utils_1.defaultWaitUntilOptions, }; this.defaultAssertOptions = { ...exports.defaultAssertOptions, }; this.contextOptions = { viewport: null, acceptDownloads: true }; this.emulatedDevice = undefined; this.customWindowSize = undefined; this.customWindowSizeOptions = devices_1.defaultWindowSizeOptions; this.showMousePosition = false; this.handleDialogs = false; this.handleTracing = false; this.tracingOptions = exports.defaultTracingOptions; this.sentRequests = []; this.failedRequests = []; this.pageErrors = []; this._mocksContext = {}; this._allMocks = []; if (browser && pageOrFrame) { this.browser = browser; this.page = (0, utils_1.toPage)(pageOrFrame); this.frame = (0, utils_1.toFrame)(pageOrFrame); } } currentBrowser() { return this.browser; } isDialogOpened() { return this._isDialogOpened; } isDialogClosed() { return this._isDialogClosed; } currentDialog() { return this.dialog; } currentPage() { return this.page; } currentFrame() { return this.frame; } /** * When execution context is a frame it will returns the current playwright Frame object, * otherwise it returns the current playwright Page object * * @returns {(Page | Frame | undefined)} * @memberof PlaywrightFluent */ currentPageOrFrame() { if (this.frame) { return this.frame; } return this.page; } previousPage() { return this._previousPage; } hasBeenRedirectedToAnotherTab() { return this._hasBeenRedirectedToAnotherTab; } /** * Private context object you can use to store data shared between any steps at runtime * * @readonly * @type {unknown} * @memberof PlaywrightFluent */ get context() { return this._context; } /** * Private typed context object you can use to store data shared between any steps at runtime * * @readonly * @type {unknown} * @memberof PlaywrightFluent */ contextAs() { return this._context; } buildAssertOptionsFrom(options) { const fullOptions = { ...this.defaultAssertOptions, ...options, }; return fullOptions; } async enableTracingOnBrowserContext() { var _a, _b; if (!this.handleTracing) { return; } await ((_b = (_a = this.browserContext) === null || _a === void 0 ? void 0 : _a.tracing) === null || _b === void 0 ? void 0 : _b.start(this.tracingOptions)); } async launchBrowser(name) { const contextOptions = { ...this.contextOptions }; if (this.emulatedDevice) { contextOptions.viewport = this.emulatedDevice.viewport; contextOptions.userAgent = this.emulatedDevice.userAgent; contextOptions.hasTouch = this.emulatedDevice.hasTouch; contextOptions.isMobile = name !== 'firefox' && this.emulatedDevice.isMobile; contextOptions.screen = this.emulatedDevice.screen; this.launchOptions.args = this.launchOptions.args || []; this.launchOptions.args.push(...(0, devices_1.getBrowserArgsForDevice)(this.emulatedDevice).andBrowser(name)); } if (this.customWindowSize) { this.launchOptions.args = this.launchOptions.args || []; this.launchOptions.args.push(...(0, devices_1.getBrowserArgsForWindowSize)(this.customWindowSize, this.customWindowSizeOptions).andBrowser(name)); } if (this.handleTracing && this.tracingOptions && this.tracingOptions.tracesDir) { this.launchOptions.tracesDir = this.tracingOptions.tracesDir; } this.browser = await action.launchBrowser(name, this.launchOptions); this.browserContext = await this.browser.newContext(contextOptions); await this.enableTracingOnBrowserContext(); this.browserContext.on('page', async (p) => { this._previousPage = this.page; this.page = p; this._hasBeenRedirectedToAnotherTab = true; try { await p.waitForLoadState(); // eslint-disable-next-line no-empty } catch (error) { } }); this.page = await this.browserContext.newPage(); if (this.showMousePosition) { await action.showMousePosition(this.page); } if (this.handleDialogs) { await action.recordPageDialogs(this.page, (dialog) => { this.dialog = dialog; this._isDialogOpened = true; this._isDialogClosed = false; }); } } async gotoPreviousTab() { if (!this._previousPage) { return; } await this._previousPage.bringToFront(); const from = this._previousPage; this._previousPage = this.page; this.page = from; this._hasBeenRedirectedToAnotherTab = true; } withDefaultWaitOptions(options) { const updatedOptions = { ...this.defaultWaitOptions, ...options, }; this.defaultWaitOptions = updatedOptions; return this; } withDefaultAssertOptions(options) { const updatedOptions = { ...this.defaultAssertOptions, ...options, }; this.defaultAssertOptions = updatedOptions; return this; } withOptions(options) { const updatedOptions = { ...this.launchOptions, ...options, }; this.launchOptions = updatedOptions; return this; } withWindowSize(size, options) { const windowSizeOptions = { ...devices_1.defaultWindowSizeOptions, ...options, }; this.customWindowSize = size; this.customWindowSizeOptions = windowSizeOptions; return this; } withViewport(viewport, options) { const viewportOptions = { ...devices_1.defaultViewportOptions, ...options, }; if (viewportOptions.ciOnly && !isCI) { return this; } this.contextOptions.viewport = viewport; return this; } withBrowser(name) { const action = () => this.launchBrowser(name); this.actions.push(action); return this; } withGeolocation(location) { this.contextOptions.geolocation = location; return this; } withTimezone(timezoneId) { this.contextOptions.timezoneId = timezoneId; return this; } withExtraHttpHeaders(headers) { this.contextOptions.extraHTTPHeaders = headers; return this; } ignoreHttpsErrors() { this.contextOptions.ignoreHTTPSErrors = true; return this; } withPermissions(...permissions) { this.contextOptions.permissions = permissions; return this; } withStorageState(storageStateFile) { this.contextOptions.storageState = storageStateFile; return this; } /** * Show mouse position with a non intrusive cursor * * @returns {PlaywrightFluent} * @memberof PlaywrightFluent */ withCursor() { this.showMousePosition = true; return this; } /** * Subscribe to page Dialogs events, so that you can act and assert on opened dialogs. * * @returns {PlaywrightFluent} * @memberof PlaywrightFluent */ WithDialogs() { this.handleDialogs = true; return this; } /** * Enable tracing API. * Playwright should be installed with a version >= 1.12.0 * * @param {Partial<TracingOptions>} [options] * @returns {PlaywrightFluent} * @memberof PlaywrightFluent * @example * const p = new PlaywrightFluent(); * const tracePath = path.join(__dirname, 'trace1.zip'); * await p * .withBrowser('chromium') * .withOptions({ headless: true }) * .withCursor() * .withTracing() * .startTracing({title: 'my first trace'}) * .navigateTo(url) * ... * .stopTracingAndSaveTrace({path: tracePath}) * .close(); * */ withTracing(options) { this.handleTracing = true; this.tracingOptions = { ...this.tracingOptions, ...options, }; return this; } async startTracingChunk(options) { var _a, _b; await ((_b = (_a = this.browserContext) === null || _a === void 0 ? void 0 : _a.tracing) === null || _b === void 0 ? void 0 : _b.startChunk(options)); } async stopTracingChunk(options) { var _a, _b; await ((_b = (_a = this.browserContext) === null || _a === void 0 ? void 0 : _a.tracing) === null || _b === void 0 ? void 0 : _b.stopChunk(options)); } /** * Start a new trace chunk. * You must first enable tracing by calling the .withTrace() method. * * @param {Partial<StartTracingOptions>} [options] * @returns {PlaywrightFluent} * @memberof PlaywrightFluent * @example * const p = new PlaywrightFluent(); * const tracePath = path.join(__dirname, 'trace1.zip'); * await p * .withBrowser('chromium') * .withOptions({ headless: true }) * .withCursor() * .withTracing() * .startTracing({title: 'my first trace'}) * .navigateTo(url) * ... * .stopTracingAndSaveTrace({path: tracePath}) * .close(); * */ startTracing(options) { const startOptions = { ...options, }; if (!this.handleTracing) { throw new Error('Cannot start Tracing because Tracing is not enabled. Maybe you forgot to call the withTrace(options)'); } const action = () => this.startTracingChunk(startOptions); this.actions.push(action); return this; } /** * Stop the trace chunk and store the trace file to the specified path. * * @param {Partial<StopTracingOptions>} [options] * @returns {PlaywrightFluent} * @memberof PlaywrightFluent * @example * const p = new PlaywrightFluent(); * const tracePath = path.join(__dirname, 'trace1.zip'); * await p * .withBrowser('chromium') * .withOptions({ headless: true }) * .withCursor() * .withTracing() * .startTracing({title: 'my first trace'}) * .navigateTo(url) * ... * .stopTracingAndSaveTrace({path: tracePath}) * .close(); */ stopTracingAndSaveTrace(options) { const stopOptions = { ...options, }; if (!this.handleTracing) { throw new Error('Cannot stop Tracing and save trace file because Tracing is not enabled. Maybe you forgot to call the withTrace(options)'); } const action = () => this.stopTracingChunk(stopOptions); this.actions.push(action); return this; } /** * Enables HAR recording for all pages. * Network activity will be saved into options.path file. * If not specified, the HAR is not recorded. * Make sure to await browserContext.close() for the HAR to be saved. * * @param {HarOptions} options * @returns {PlaywrightFluent} * @memberof PlaywrightFluent * @example * const p = new PlaywrightFluent(); * const harFilepath = `${path.join(__dirname, uniqueFilename({ prefix: 'har-', extension: '.json' }))}`; * await p * .withBrowser('chromium') * .withOptions({ headless: true }) * .withCursor() * .recordNetworkActivity({ path: harFilepath }) * ... * .close(); * * const harData = p.getRecordedNetworkActivity(); * */ recordNetworkActivity(options) { this.contextOptions.recordHar = options; return this; } /** * Enables video recording into the options.dir directory. * If not specified videos are not recorded. * Make sure to await browserContext.close() for videos to be saved. * * @param {RecordVideoOptions} options * @returns {PlaywrightFluent} * @memberof PlaywrightFluent * * @example * const p = new PlaywrightFluent(); * await p * .withBrowser('chromium') * .withOptions({ headless: true }) * .withWindowSize(sizeOf._1024x768) * .clearVideoFilesOlderThan(__dirname, 60) * .recordVideo({ dir: __dirname, size: sizeOf._1024x768 }) * .navigateTo(url) * ... * .close(); * * const videoPath = await p.getRecordedVideoPath(); */ recordVideo(options) { this.contextOptions.recordVideo = options; return this; } async getRecordedVideoPath() { var _a, _b; const videoPath = await ((_b = (_a = this.page) === null || _a === void 0 ? void 0 : _a.video()) === null || _b === void 0 ? void 0 : _b.path()); return videoPath; } async clearVideoFiles(dir, durationInSeconds) { const files = (0, utils_1.getFilesOlderThanInDirectory)(dir, durationInSeconds, (path) => { return path.endsWith('.webm'); }); files.forEach((file) => { // eslint-disable-next-line no-console console.warn(`File '${file}' is about to be deleted`); (0, utils_1.deleteFile)(file); }); } /** * Remove video files generated by previous tests run * * @param {string} dir * @param {number} durationInSeconds * @returns {PlaywrightFluent} * @memberof PlaywrightFluent */ clearVideoFilesOlderThan(dir, durationInSeconds) { const action = () => this.clearVideoFiles(dir, durationInSeconds); this.actions.push(action); return this; } /** * Get HAR data as json data. * Call this method only after the browser is closed. * @param {string} [filepath] optional * @returns {HarData} * @memberof PlaywrightFluent */ getRecordedNetworkActivity(filepath) { var _a; const harFilePath = filepath || ((_a = this.contextOptions.recordHar) === null || _a === void 0 ? void 0 : _a.path); return (0, utils_1.getHarDataFrom)(harFilePath); } async closeBrowser(options) { await action.closeBrowser(this.currentBrowser(), options); } close(options) { const closeOptions = { ...actions_1.defaultCloseOptions, ...options, }; const action = () => this.closeBrowser(closeOptions); this.actions.push(action); return this; } switchToPreviousTab() { const action = () => this.gotoPreviousTab(); this.actions.push(action); return this; } getRecordedRequestsTo(url) { return [...this.sentRequests.filter((req) => req.url().includes(url))]; } getLastRecordedRequestTo(url) { return this.sentRequests.filter((req) => req.url().includes(url)).pop(); } clearRecordedRequestsTo(url) { this.sentRequests = [...this.sentRequests.filter((req) => !req.url().includes(url))]; } async recordRequestsToUrl(partialUrl, ignorePredicate) { await action.recordRequestsTo(partialUrl, ignorePredicate, this.currentPage(), (request) => this.sentRequests.push(request)); } /** * Will track and record requests whose url contains the input url. * Usefull when you need to check what the front sends to the back and/or what the back sends to the front. * Each recorded request is a standard `playwright` request object that contains both the request and the response. * * @param {string} partialUrl This parameter should be seen as a partial url (it is not a regex and not a glob pattern). * @param {(request: Request) => boolean} [ignorePredicate=() => false] optional predicate to provide if you want to ignore specific requests * @returns {PlaywrightFluent} * @memberof PlaywrightFluent * @example * const p = new PlaywrightFluent(); * await p * .withBrowser('chromium') * .withOptions({ headless: true }) * .withCursor() * .recordRequestsTo('/api/v1/user', (request) => request.method() === 'OPTIONS') * .recordRequestsTo('/api/v1/books') * .navigateTo(url); */ recordRequestsTo(partialUrl, ignorePredicate = () => false) { const action = () => this.recordRequestsToUrl(partialUrl, ignorePredicate); this.actions.push(action); return this; } async recordDownloadsToDirectory(directory) { await action.recordDownloadsTo(directory, this.currentPage()); } /** * Save all downloads in directory. * * @param {string} directory * @returns {PlaywrightFluent} * @memberof PlaywrightFluent * * @example * const p = new PlaywrightFluent(); * const expectedDownloadedFilepath = path.join(userDownloadsDirectory, 'download.zip'); * await p * .withBrowser('chromium') * .withOptions({ headless: true }) * .withCursor() * .recordDownloadsTo(userDownloadsDirectory) * .navigateTo(url) * .click('a#download-package') * .waitUntil(async () => fileExists(expectedDownloadedFilepath)); */ recordDownloadsTo(directory) { const action = () => this.recordDownloadsToDirectory(directory); this.actions.push(action); return this; } async delayRequestsToUrl(partialUrl, delayInSeconds) { await action.delayRequestsTo(partialUrl, delayInSeconds, this.currentPage()); } delayRequestsTo(partialUrl, delayInSeconds) { const action = () => this.delayRequestsToUrl(partialUrl, delayInSeconds); this.actions.push(action); return this; } async onRequestToRespondWith(partialUrl, options, response) { await action.onRequestToRespondWith(partialUrl, options, response, this.currentPage()); } async onRequestToRespondFromHar(partialUrl, harFiles, options) { await action.onRequestToRespondFromHar(partialUrl, harFiles, this.currentPage(), options); } onRequestTo(partialUrl, options = {}) { return { respondWith: (response) => { const action = () => this.onRequestToRespondWith(partialUrl, options, response); this.actions.push(action); return this; }, respondFromHar: (harFiles, options) => { const harOptions = { ...actions_1.defaultHarRequestResponseOptions, ...options, }; const action = () => this.onRequestToRespondFromHar(partialUrl, harFiles, harOptions); this.actions.push(action); return this; }, }; } getFailedRequests() { return [...this.failedRequests]; } clearFailedRequests() { this.failedRequests = []; } async recordAllFailedRequests() { await action.recordFailedRequests(this.currentPage(), (request) => this.failedRequests.push(request)); } recordFailedRequests() { const action = () => this.recordAllFailedRequests(); this.actions.push(action); return this; } getPageErrors() { return [...this.pageErrors]; } clearPageErrors() { this.pageErrors = []; } async recordUncaughtExceptions() { await action.recordPageErrors(this.currentPage(), (err) => this.pageErrors.push(err)); } recordPageErrors() { const action = () => this.recordUncaughtExceptions(); this.actions.push(action); return this; } async saveStorageStateToFile(targetFile) { var _a; await ((_a = this.browserContext) === null || _a === void 0 ? void 0 : _a.storageState({ path: targetFile })); } /** * Will save the storage state in a local file * * @param {string} targetFile : The file path to save the storage state to. If path is a relative path, then it is resolved relative to current working directory * @returns {PlaywrightFluent} * @memberof PlaywrightFluent */ saveStorageStateTo(targetFile) { const action = () => this.saveStorageStateToFile(targetFile); this.actions.push(action); return this; } /** * Get current Playwright Storage State * * @returns {(Promise<StorageState | undefined>)} * @memberof PlaywrightFluent */ async currentStorageState() { var _a; return await ((_a = this.browserContext) === null || _a === void 0 ? void 0 : _a.storageState()); } async pauseExecution() { await action.pause(this.currentPage()); } pause() { const action = () => this.pauseExecution(); this.actions.push(action); return this; } async waitForDialogToOpen(options) { await action.waitForDialog(() => this.dialog, this.currentPage(), options); } /** * Wait for a dialog to open. * * @param {Partial<WaitUntilOptions>} [options={}] * @returns {PlaywrightFluent} * @memberof PlaywrightFluent */ waitForDialog(options = {}) { const waitUntilOptions = { ...utils_1.defaultWaitUntilOptions, ...this.defaultWaitOptions, ...options, }; const action = () => this.waitForDialogToOpen(waitUntilOptions); this.actions.push(action); return this; } async cancelCurrentDialog() { await action.cancelDialog(this.dialog, this.currentPage(), () => { this.dialog = undefined; this._isDialogClosed = true; this._isDialogOpened = false; }); } cancelDialog() { const action = () => this.cancelCurrentDialog(); this.actions.push(action); return this; } async acceptCurrentDialog() { await action.acceptDialog(this.dialog, undefined, this.currentPage(), () => { this.dialog = undefined; this._isDialogClosed = true; this._isDialogOpened = false; }); } acceptDialog() { const action = () => this.acceptCurrentDialog(); this.actions.push(action); return this; } async typeTextInCurrentDialogAndSubmit(text) { await action.acceptDialog(this.dialog, text, this.currentPage(), () => { this.dialog = undefined; this._isDialogClosed = true; this._isDialogOpened = false; }); } typeTextInDialogAndSubmit(text) { const action = () => this.typeTextInCurrentDialogAndSubmit(text); this.actions.push(action); return this; } /** * context shared between all mocks. * * @readonly * @type {unknown} * @memberof PlaywrightFluent */ get mocksContext() { return this._mocksContext; } /** * typed context shared between all mocks. * * @template T * @returns {T} * @memberof PlaywrightFluent */ mocksContextAs() { return this._mocksContext; } async registerMocks(options) { await action.withMocks(() => this._allMocks, () => this._mocksContext, options, this.currentPage()); } /** * Provide a set of mocks in order to automatically handle request interceptions * This method can be called multiple times with different set of mocks: * in this case all mocks are concatenated in a single internal array. * * * @template T * @param {() => Partial<FluentMock<T>[]} mocks * @param {Partial<WithMocksOptions>} [options=defaultMocksOptions] * @returns {PlaywrightFluent} * @memberof PlaywrightFluent */ withMocks(mocks, options = actions_1.defaultMocksOptions) { mocks.forEach(actions_1.validateMock); if (this._allMocks.length === 0) { const action = () => this.registerMocks(options); this.actions.push(action); } this._allMocks = this._allMocks.concat(mocks); return this; } removeMocksWithDisplayName(displayName) { this._allMocks = this._allMocks.filter((mock) => mock.displayName !== displayName); return this; } hasMockWithDisplayName(displayName) { return this._allMocks.some((mock) => mock.displayName === displayName); } /** * Get all mocks with the given displayName * * @param {(string | undefined)} displayName * @return {*} {(Partial<FluentMock>[] | undefined)} * @memberof PlaywrightFluent */ getAllMocksWithDisplayName(displayName) { return this._allMocks.filter((mock) => mock.displayName === displayName); } /** * Get the last mock with the given displayName * * @param {(string | undefined)} displayName * @return {*} {(Partial<FluentMock> | undefined)} * @memberof PlaywrightFluent */ getLastMockWithDisplayName(displayName) { return [...this._allMocks.filter((mock) => mock.displayName === displayName)].pop(); } async gotoUrl(url, options) { await action.navigateTo(url, options, this.currentPageOrFrame()); } navigateTo(url, options) { const navigationOptions = { ...actions_1.defaultNavigationOptions, ...options, }; const action = () => this.gotoUrl(url, navigationOptions); this.actions.push(action); return this; } async hoverOnSelector(selector, options) { await action.hoverOnSelector(selector, this.currentPageOrFrame(), options); } async hoverOnSelectorObject(selector, options) { await action.hoverOnSelectorObject(selector, this.currentPageOrFrame(), options); } hover(selector, options) { const hoverOptions = { ...actions_1.defaultHoverOptions, ...this.defaultWaitOptions, ...options, }; if (typeof selector === 'string') { const action = () => this.hoverOnSelector(selector, hoverOptions); this.actions.push(action); return this; } { const action = () => this.hoverOnSelectorObject(selector, hoverOptions); this.actions.push(action); return this; } } async switchFromSelectorToIframe(selector, options) { this.frame = await action.switchFromSelectorToIframe(selector, () => this.currentPageOrFrame(), { ...options, injectCursor: this.showMousePosition, }); } async switchFromSelectorObjectToIframe(selector, options) { this.frame = await action.switchFromSelectorObjectToIframe(selector, () => this.currentPageOrFrame(), { ...options, injectCursor: this.showMousePosition, }); } /** * Will switch inside the iframe targeted by the specified selector * * @param {(string | SelectorFluent)} selector * @param {Partial<SwitchToIframeOptions>} [options] * @returns {PlaywrightFluent} * @memberof PlaywrightFluent * * @example * const p = new PlaywrightFluent(); * const selector = 'iframe'; * const inputInIframe = '#input-inside-iframe'; * const inputInMainPage = '#input-in-main-page'; * await p * .withBrowser('chromium') * .withOptions({ headless: false }) * .withCursor() * .navigateTo(url) * .hover(selector) * .switchToIframe(selector) * .click(inputInIframe) * .typeText('hey I am in the iframe') * .switchBackToPage() * .click(inputInMainPage) * .typeText('hey I am back in the page!'); */ switchToIframe(selector, options) { const switchToIframeOptions = { ...actions_1.defaultSwitchToIframeOptions, ...this.defaultWaitOptions, ...options, }; if (typeof selector === 'string') { const action = () => this.switchFromSelectorToIframe(selector, switchToIframeOptions); this.actions.push(action); return this; } { const action = () => this.switchFromSelectorObjectToIframe(selector, switchToIframeOptions); this.actions.push(action); return this; } } async switchBackFromIframeToCurrentPage() { this.frame = undefined; } /** * Will switch from the current Iframe back to the current page. * * @returns {PlaywrightFluent} * @memberof PlaywrightFluent */ switchBackToPage() { const action = () => this.switchBackFromIframeToCurrentPage(); this.actions.push(action); return this; } async clickOnSelector(selector, options) { await action.clickOnSelector(selector, this.currentPageOrFrame(), options); } async clickOnSelectorObject(selector, options) { await action.clickOnSelectorObject(selector, this.currentPageOrFrame(), options); } click(selector, options) { const clickOptions = { ...actions_1.defaultClickOptions, ...this.defaultWaitOptions, ...options, }; if (typeof selector === 'string') { const action = () => this.clickOnSelector(selector, clickOptions); this.actions.push(action); return this; } { const action = () => this.clickOnSelectorObject(selector, clickOptions); this.actions.push(action); return this; } } async doubleClickOnSelector(selector, options) { await action.doubleClickOnSelector(selector, this.currentPageOrFrame(), options); } async doubleClickOnSelectorObject(selector, options) { await action.doubleClickOnSelectorObject(selector, this.currentPageOrFrame(), options); } doubleClick(selector, options) { const doubleClickOptions = { ...actions_1.defaultDoubleClickOptions, ...this.defaultWaitOptions, ...options, }; if (typeof selector === 'string') { const action = () => this.doubleClickOnSelector(selector, doubleClickOptions); this.actions.push(action); return this; } { const action = () => this.doubleClickOnSelectorObject(selector, doubleClickOptions); this.actions.push(action); return this; } } async clickAtPositionOnPage(position, options) { await action.clickAtPosition(position, this.currentPage(), options); } clickAtPosition(position, options) { const clickOptions = { ...actions_1.defaultClickOptions, ...this.defaultWaitOptions, ...options, }; const action = () => this.clickAtPositionOnPage(position, clickOptions); this.actions.push(action); return this; } async checkSelector(selector, options) { await action.checkSelector(selector, this.currentPageOrFrame(), options); } async checkSelectorObject(selector, options) { await action.checkSelectorObject(selector, this.currentPageOrFrame(), options); } check(selector, options) { const checkOptions = { ...actions_1.defaultCheckOptions, ...this.defaultWaitOptions, ...options, }; if (typeof selector === 'string') { const action = () => this.checkSelector(selector, checkOptions); this.actions.push(action); return this; } { const action = () => this.checkSelectorObject(selector, checkOptions); this.actions.push(action); return this; } } async uncheckSelector(selector, options) { await action.uncheckSelector(selector, this.currentPageOrFrame(), options); } async uncheckSelectorObject(selector, options) { await action.uncheckSelectorObject(selector, this.currentPageOrFrame(), options); } uncheck(selector, options) { const checkOptions = { ...actions_1.defaultCheckOptions, ...this.defaultWaitOptions, ...options, }; if (typeof selector === 'string') { const action = () => this.uncheckSelector(selector, checkOptions); this.actions.push(action); return this; } { const action = () => this.uncheckSelectorObject(selector, checkOptions); this.actions.push(action); return this; } } async selectOptionsInSelector(selector, labels, options) { await action.selectOptionsInSelector(selector, labels, this.currentPageOrFrame(), options); } async selectOptionsInFocusedSelector(labels, options) { await action.selectOptionsInFocused(labels, this.currentPageOrFrame(), options); } async selectOptionsInSelectorObject(selector, labels, options) { await action.selectOptionsInSelectorObject(selector, labels, this.currentPageOrFrame(), options); } select(...labels) { return { in: (selector, options) => { const selectOptions = { ...actions_1.defaultSelectOptions, ...this.defaultWaitOptions, ...options, }; if (typeof selector === 'string') { const action = () => this.selectOptionsInSelector(selector, labels, selectOptions); this.actions.push(action); return this; } { const action = () => this.selectOptionsInSelectorObject(selector, labels, selectOptions); this.actions.push(action); return this; } }, inFocused: (options) => { const selectOptions = { ...actions_1.defaultSelectOptions, ...this.defaultWaitOptions, ...options, }; const action = () => this.selectOptionsInFocusedSelector(labels, selectOptions); this.actions.push(action); return this; }, }; } async selectOptionsByValueInFocusedSelector(values, options) { await action.selectOptionsByValueInFocused(values, this.currentPageOrFrame(), options); } async selectOptionsByValueInSelector(selector, values, options) { await action.selectOptionsByValueInSelector(selector, values, this.currentPageOrFrame(), options); } async selectOptionsByValueInSelectorObject(selector, values, options) { await action.selectOptionsByValueInSelectorObject(selector, values, this.currentPageOrFrame(), options); } selectByValue(...values) { return { in: (selector, options) => { const selectOptions = { ...actions_1.defaultSelectOptions, ...this.defaultWaitOptions, ...options, }; if (typeof selector === 'string') { const action = () => this.selectOptionsByValueInSelector(selector, values, selectOptions); this.actions.push(action); return this; } { const action = () => this.selectOptionsByValueInSelectorObject(selector, values, selectOptions); this.actions.push(action); return this; } }, inFocused: (options) => { const selectOptions = { ...actions_1.defaultSelectOptions, ...this.defaultWaitOptions, ...options, }; const action = () => this.selectOptionsByValueInFocusedSelector(values, selectOptions); this.actions.push(action); return this; }, }; } /** * Emulate device * * @param {DeviceName} deviceName * @returns {PlaywrightFluent} * @memberof PlaywrightFluent */ emulateDevice(deviceName) { const device = (0, devices_1.getDevice)(deviceName); if (!device) { throw new Error(`device '${deviceName}' is unknown. It must be one of : [${devices_1.allKnownDevices .map((d) => d.name) .join(';')}] `); } this.emulatedDevice = device; return this; } async typeTextInFocusedElement(text, options) { await action.typeText(text, this.currentPageOrFrame(), options); } /** * Type text in the element that has current focus. * * @param {string} text * @param {Partial<TypeTextOptions>} [options=defaultTypeTextOptions] * @returns {PlaywrightFluent} * @memberof PlaywrightFluent */ typeText(text, options) { const typeTextOptions = { ...actions_1.defaultTypeTextOptions, ...options, }; const action = () => this.typeTextInFocusedElement(text, typeTextOptions); this.actions.push(action); return this; } async clearTextInFocusedElement(options) { await action.clearText(this.currentPageOrFrame(), options); } /** * Clear text in the element that has current focus. * * @param {Partial<ClearTextOptions>} [options=defaultClearTextOptions] * @returns {PlaywrightFluent} * @memberof PlaywrightFluent */ clearText(options) { const clearTextOptions = { ...actions_1.defaultClearTextOptions, ...options, }; const action = () => this.clearTextInFocusedElement(clearTextOptions); this.actions.push(action); return this; } /** * Alias for clearText. * * @param {Partial<ClearTextOptions>} [options=defaultClearTextOptions] * @returns {PlaywrightFluent} * @memberof PlaywrightFluent */ clear(options) { return this.clearText(options); } async invokeMethodOnSelector(methodName, selector, options) { await action.invokeMethodOnSelector(methodName, selector, this.currentPageOrFrame(), options); } async invokeMethodOnSelectorObject(methodName, selector, options) { await action.invokeMethodOnSelectorObject(methodName, selector, options); } /** * Be able to invoke a native method on a selector. * Use this action only in edge cases * where the selector itself is hidden because of its transprency, * or because it has null dimension, * and the normal click does not work neither on this selector nor on it's parent. * * @param {MethodName} methodName * @param {(string | SelectorFluent)} selector * @param {Partial<InvokeOptions>} [options] * @returns {PlaywrightFluent} * @memberof PlaywrightFluent */ invokeMethod(methodName, selector, options) { const invokeOptions = { ...actions_1.defaultInvokeOptions, ...this.defaultWaitOptions, ...options, }; if (typeof selector === 'string') { const action = () => this.invokeMethodOnSelector(methodName, selector, invokeOptions); this.actions.push(action); return this; } { const action = () => this.invokeMethodOnSelectorObject(methodName, selector, invokeOptions); this.actions.push(action); return this; } } async pasteTextInFocusedElement(text, options) { await action.pasteText(text, this.currentPageOrFrame(), options); } /** * Paste text in the element that has current focus. * * @param {string} text * @param {Partial<PasteTextOptions>} [options=defaultPasteTextOptions] * @returns {PlaywrightFluent} * @memberof PlaywrightFluent */ pasteText(text, options) { const pasteTextOptions = { ...actions_1.defaultPasteTextOptions, ...options, }; const action = () => this.pasteTextInFocusedElement(text, pasteTextOptions); this.actions.push(action); return this; } async pressOnKey(key, options) { await action.pressKey(key, this.currentPageOrFrame(), options); } pressKey(key, options) { const pressKeyOptions = { ...actions_1.defaultKeyboardPressOptions, ...options, }; const action = () => this.pressOnKey(key, pressKeyOptions); this.actions.push(action); return this; } async holdKey(key) { await action.holdDownKey(key, this.currentPageOrFrame()); } holdDownKey(key) { const action = () => this.holdKey(key); this.actions.push(action); return this; } async releaseHoldKey(key) { await action.releaseKey(key, this.currentPageOrFrame()); } releaseKey(key) { const action = () => this.releaseHoldKey(key); this.actions.push(action); return this; } wait(durationInMilliseconds) { this.actions.push(async () => await (0, utils_1.sleep)(durationInMilliseconds)); return this; } async takeFullPageScreenshotAsBase64(options) { const screenshotOptions = { ...actions_1.defaultFullPageScreenshotOptions, ...options, }; const result = await action.takeFullPageScreenshotAsBase64(this.currentPage(), screenshotOptions); return result; } registerStory(story, param) { if (typeof story !== 'function') { throw new Error('Story should be a function'); } if (param === undefined) { this.actions.push(async () => await story(this)); return this; } this.actions.push(async () => await story(this, param)); return this; } runStory(story, param) { return this.registerStory(story, param); } attemptsTo(story, param) { return this.registerStory(story, param); } and(story, param) { return this.registerStory(story, param); } do(story, param) { return this.registerStory(story, param); } verifyIf(story, param) { return this.registerStory(story, param); } /** * Wait until predicate becomes true, * and always return true during 300 ms. * The waiting mechanism can be modified by setting options * * @param {() => Promise<boolean>} predicate * @param {Partial<WaitUntilOptions>} [options=defaultWaitUntilOptions] * @param {(string | (() => Promise<string>))} errorMessage * @returns {PlaywrightFluent} * @memberof PlaywrightFluent */ waitUntil(predicate, options = {}, errorMessage) { const waitUntilOptions = { ...utils_1.defaultWaitUntilOptions, ...this.defaultWaitOptions, ...options, }; const defaultErrorMessage = `Predicate still resolved to false after ${waitUntilOptions.timeoutInMilliseconds} ms.`; const errorMessageOrDefault = errorMessage || defaultErrorMessage; this.actions.push(async () => { await (0, utils_1.waitUntil)(predi