playwright-fluent
Version:
Fluent API around playwright
1,321 lines • 74.3 kB
JavaScript
"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