codeceptjs 3.4.1 → 3.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +85 -0
- package/README.md +11 -9
- package/bin/codecept.js +1 -1
- package/docs/ai.md +248 -0
- package/docs/build/Appium.js +47 -7
- package/docs/build/JSONResponse.js +4 -4
- package/docs/build/Nightmare.js +3 -1
- package/docs/build/OpenAI.js +122 -0
- package/docs/build/Playwright.js +234 -54
- package/docs/build/Protractor.js +3 -1
- package/docs/build/Puppeteer.js +101 -12
- package/docs/build/REST.js +15 -5
- package/docs/build/TestCafe.js +61 -2
- package/docs/build/WebDriver.js +85 -5
- package/docs/changelog.md +85 -0
- package/docs/helpers/Appium.md +152 -147
- package/docs/helpers/JSONResponse.md +4 -4
- package/docs/helpers/Nightmare.md +2 -0
- package/docs/helpers/OpenAI.md +70 -0
- package/docs/helpers/Playwright.md +228 -151
- package/docs/helpers/Puppeteer.md +153 -101
- package/docs/helpers/REST.md +6 -5
- package/docs/helpers/TestCafe.md +97 -49
- package/docs/helpers/WebDriver.md +159 -107
- package/docs/mobile.md +49 -2
- package/docs/parallel.md +56 -0
- package/docs/plugins.md +87 -33
- package/docs/secrets.md +6 -0
- package/docs/tutorial.md +2 -2
- package/docs/webapi/appendField.mustache +2 -0
- package/docs/webapi/blur.mustache +17 -0
- package/docs/webapi/focus.mustache +12 -0
- package/docs/webapi/type.mustache +3 -0
- package/lib/ai.js +171 -0
- package/lib/cli.js +10 -2
- package/lib/codecept.js +4 -0
- package/lib/command/dryRun.js +9 -1
- package/lib/command/generate.js +46 -3
- package/lib/command/init.js +23 -1
- package/lib/command/interactive.js +15 -1
- package/lib/command/run-workers.js +2 -1
- package/lib/container.js +13 -3
- package/lib/event.js +2 -0
- package/lib/helper/Appium.js +45 -7
- package/lib/helper/JSONResponse.js +4 -4
- package/lib/helper/Nightmare.js +1 -1
- package/lib/helper/OpenAI.js +122 -0
- package/lib/helper/Playwright.js +200 -45
- package/lib/helper/Protractor.js +1 -1
- package/lib/helper/Puppeteer.js +67 -12
- package/lib/helper/REST.js +15 -5
- package/lib/helper/TestCafe.js +30 -2
- package/lib/helper/WebDriver.js +51 -5
- package/lib/helper/scripts/blurElement.js +17 -0
- package/lib/helper/scripts/focusElement.js +17 -0
- package/lib/helper/scripts/highlightElement.js +20 -0
- package/lib/html.js +258 -0
- package/lib/interfaces/gherkin.js +8 -0
- package/lib/listener/retry.js +2 -1
- package/lib/pause.js +73 -17
- package/lib/plugin/debugErrors.js +67 -0
- package/lib/plugin/fakerTransform.js +4 -6
- package/lib/plugin/heal.js +177 -0
- package/lib/plugin/screenshotOnFail.js +11 -2
- package/lib/recorder.js +11 -8
- package/lib/secret.js +5 -4
- package/lib/step.js +6 -1
- package/lib/ui.js +4 -3
- package/lib/utils.js +17 -0
- package/lib/workers.js +57 -9
- package/package.json +25 -16
- package/translations/ja-JP.js +9 -9
- package/typings/index.d.ts +43 -9
- package/typings/promiseBasedTypes.d.ts +242 -25
- package/typings/types.d.ts +260 -35
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
const Helper = require('@codeceptjs/helper');
|
|
2
|
+
const AiAssistant = require('../ai');
|
|
3
|
+
const standardActingHelpers = require('../plugin/standardActingHelpers');
|
|
4
|
+
const Container = require('../container');
|
|
5
|
+
const { splitByChunks, minifyHtml } = require('../html');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* OpenAI Helper for CodeceptJS.
|
|
9
|
+
*
|
|
10
|
+
* This helper class provides integration with the OpenAI GPT-3.5 or 4 language model for generating responses to questions or prompts within the context of web pages. It allows you to interact with the GPT-3.5 model to obtain intelligent responses based on HTML fragments or general prompts.
|
|
11
|
+
* This helper should be enabled with any web helpers like Playwright or Puppeteer or WebDrvier to ensure the HTML context is available.
|
|
12
|
+
*
|
|
13
|
+
* ## Configuration
|
|
14
|
+
*
|
|
15
|
+
* This helper should be configured in codecept.json or codecept.conf.js
|
|
16
|
+
*
|
|
17
|
+
* * `chunkSize`: (optional, default: 80000) - The maximum number of characters to send to the OpenAI API at once. We split HTML fragments by 8000 chars to not exceed token limit. Increase this value if you use GPT-4.
|
|
18
|
+
*/
|
|
19
|
+
class OpenAI extends Helper {
|
|
20
|
+
constructor(config) {
|
|
21
|
+
super(config);
|
|
22
|
+
this.aiAssistant = new AiAssistant();
|
|
23
|
+
|
|
24
|
+
this.options = {
|
|
25
|
+
chunkSize: 80000,
|
|
26
|
+
};
|
|
27
|
+
this.options = { ...this.options, ...config };
|
|
28
|
+
|
|
29
|
+
const helpers = Container.helpers();
|
|
30
|
+
|
|
31
|
+
for (const helperName of standardActingHelpers) {
|
|
32
|
+
if (Object.keys(helpers).indexOf(helperName) > -1) {
|
|
33
|
+
this.helper = helpers[helperName];
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Asks the OpenAI GPT language model a question based on the provided prompt within the context of the current page's HTML.
|
|
41
|
+
*
|
|
42
|
+
* ```js
|
|
43
|
+
* I.askGptOnPage('what does this page do?');
|
|
44
|
+
* ```
|
|
45
|
+
*
|
|
46
|
+
* @async
|
|
47
|
+
* @param {string} prompt - The question or prompt to ask the GPT model.
|
|
48
|
+
* @returns {Promise<string>} - A Promise that resolves to the generated responses from the GPT model, joined by newlines.
|
|
49
|
+
*/
|
|
50
|
+
async askGptOnPage(prompt) {
|
|
51
|
+
const html = await this.helper.grabSource();
|
|
52
|
+
|
|
53
|
+
const htmlChunks = splitByChunks(html, this.options.chunkSize);
|
|
54
|
+
|
|
55
|
+
if (htmlChunks.length > 1) this.debug(`Splitting HTML into ${htmlChunks.length} chunks`);
|
|
56
|
+
|
|
57
|
+
const responses = [];
|
|
58
|
+
|
|
59
|
+
for (const chunk of htmlChunks) {
|
|
60
|
+
const messages = [
|
|
61
|
+
{ role: 'user', content: prompt },
|
|
62
|
+
{ role: 'user', content: `Within this HTML: ${minifyHtml(chunk)}` },
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
if (htmlChunks.length > 1) messages.push({ role: 'user', content: 'If action is not possible on this page, do not propose anything, I will send another HTML fragment' });
|
|
66
|
+
|
|
67
|
+
const response = await this.aiAssistant.createCompletion(messages);
|
|
68
|
+
|
|
69
|
+
console.log(response);
|
|
70
|
+
|
|
71
|
+
responses.push(response);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return responses.join('\n\n');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Asks the OpenAI GPT-3.5 language model a question based on the provided prompt within the context of a specific HTML fragment on the current page.
|
|
79
|
+
*
|
|
80
|
+
* ```js
|
|
81
|
+
* I.askGptOnPageFragment('describe features of this screen', '.screen');
|
|
82
|
+
* ```
|
|
83
|
+
*
|
|
84
|
+
* @async
|
|
85
|
+
* @param {string} prompt - The question or prompt to ask the GPT-3.5 model.
|
|
86
|
+
* @param {string} locator - The locator or selector used to identify the HTML fragment on the page.
|
|
87
|
+
* @returns {Promise<string>} - A Promise that resolves to the generated response from the GPT model.
|
|
88
|
+
*/
|
|
89
|
+
async askGptOnPageFragment(prompt, locator) {
|
|
90
|
+
const html = await this.helper.grabHTMLFrom(locator);
|
|
91
|
+
|
|
92
|
+
const messages = [
|
|
93
|
+
{ role: 'user', content: prompt },
|
|
94
|
+
{ role: 'user', content: `Within this HTML: ${minifyHtml(html)}` },
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
const response = await this.aiAssistant.createCompletion(messages);
|
|
98
|
+
|
|
99
|
+
console.log(response);
|
|
100
|
+
|
|
101
|
+
return response;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Send a general request to ChatGPT and return response.
|
|
106
|
+
* @param {string} prompt
|
|
107
|
+
* @returns {Promise<string>} - A Promise that resolves to the generated response from the GPT model.
|
|
108
|
+
*/
|
|
109
|
+
async askGptGeneralPrompt(prompt) {
|
|
110
|
+
const messages = [
|
|
111
|
+
{ role: 'user', content: prompt },
|
|
112
|
+
];
|
|
113
|
+
|
|
114
|
+
const completion = await this.aiAssistant.createCompletion(messages);
|
|
115
|
+
|
|
116
|
+
const response = completion?.data?.choices[0]?.message?.content;
|
|
117
|
+
|
|
118
|
+
console.log(response);
|
|
119
|
+
|
|
120
|
+
return response;
|
|
121
|
+
}
|
|
122
|
+
}
|
package/docs/build/Playwright.js
CHANGED
|
@@ -2,7 +2,9 @@ const path = require('path');
|
|
|
2
2
|
const fs = require('fs');
|
|
3
3
|
|
|
4
4
|
const Helper = require('@codeceptjs/helper');
|
|
5
|
+
const { v4: uuidv4 } = require('uuid');
|
|
5
6
|
const Locator = require('../locator');
|
|
7
|
+
const store = require('../store');
|
|
6
8
|
const recorder = require('../recorder');
|
|
7
9
|
const stringIncludes = require('../assert/include').includes;
|
|
8
10
|
const { urlEquals } = require('../assert/equal');
|
|
@@ -43,19 +45,20 @@ const {
|
|
|
43
45
|
setRestartStrategy, restartsSession, restartsContext, restartsBrowser,
|
|
44
46
|
} = require('./extras/PlaywrightRestartOpts');
|
|
45
47
|
const { createValueEngine, createDisabledEngine } = require('./extras/PlaywrightPropEngine');
|
|
48
|
+
const { highlightElement } = require('./scripts/highlightElement');
|
|
46
49
|
|
|
47
50
|
const pathSeparator = path.sep;
|
|
48
51
|
|
|
49
52
|
/**
|
|
50
53
|
* ## Configuration
|
|
51
54
|
*
|
|
52
|
-
* This helper should be configured in codecept.conf.js
|
|
55
|
+
* This helper should be configured in codecept.conf.(js|ts)
|
|
53
56
|
*
|
|
54
57
|
* @typedef PlaywrightConfig
|
|
55
58
|
* @type {object}
|
|
56
|
-
* @prop {string} url - base url of website to be tested
|
|
57
|
-
* @prop {
|
|
58
|
-
* @prop {boolean} [show=
|
|
59
|
+
* @prop {string} [url] - base url of website to be tested
|
|
60
|
+
* @prop {'chromium' | 'firefox'| 'webkit' | 'electron'} [browser='chromium'] - a browser to test on, either: `chromium`, `firefox`, `webkit`, `electron`. Default: chromium.
|
|
61
|
+
* @prop {boolean} [show=true] - show browser window.
|
|
59
62
|
* @prop {string|boolean} [restart=false] - restart strategy between tests. Possible values:
|
|
60
63
|
* * 'context' or **false** - restarts [browser context](https://playwright.dev/docs/api/class-browsercontext) but keeps running browser. Recommended by Playwright team to keep tests isolated.
|
|
61
64
|
* * 'browser' or **true** - closes browser and opens it again between tests.
|
|
@@ -72,13 +75,13 @@ const pathSeparator = path.sep;
|
|
|
72
75
|
* @prop {boolean} [keepBrowserState=false] - keep browser state between tests when `restart` is set to 'session'.
|
|
73
76
|
* @prop {boolean} [keepCookies=false] - keep cookies between tests when `restart` is set to 'session'.
|
|
74
77
|
* @prop {number} [waitForAction] - how long to wait after click, doubleClick or PressKey actions in ms. Default: 100.
|
|
75
|
-
* @prop {
|
|
78
|
+
* @prop {'load' | 'domcontentloaded' | 'networkidle'} [waitForNavigation] - When to consider navigation succeeded. Possible options: `load`, `domcontentloaded`, `networkidle`. Choose one of those options is possible. See [Playwright API](https://playwright.dev/docs/api/class-page#page-wait-for-navigation).
|
|
76
79
|
* @prop {number} [pressKeyDelay=10] - Delay between key presses in ms. Used when calling Playwrights page.type(...) in fillField/appendField
|
|
77
80
|
* @prop {number} [getPageTimeout] - config option to set maximum navigation time in milliseconds.
|
|
78
81
|
* @prop {number} [waitForTimeout] - default wait* timeout in ms. Default: 1000.
|
|
79
82
|
* @prop {object} [basicAuth] - the basic authentication to pass to base url. Example: {username: 'username', password: 'password'}
|
|
80
83
|
* @prop {string} [windowSize] - default window size. Set a dimension like `640x480`.
|
|
81
|
-
* @prop {
|
|
84
|
+
* @prop {'dark' | 'light' | 'no-preference'} [colorScheme] - default color scheme. Possible values: `dark` | `light` | `no-preference`.
|
|
82
85
|
* @prop {string} [userAgent] - user-agent string.
|
|
83
86
|
* @prop {string} [locale] - locale string. Example: 'en-GB', 'de-DE', 'fr-FR', ...
|
|
84
87
|
* @prop {boolean} [manualStart] - do not start browser before a test, start it manually inside a helper with `this.helpers["Playwright"]._startBrowser()`.
|
|
@@ -88,6 +91,8 @@ const pathSeparator = path.sep;
|
|
|
88
91
|
* @prop {any} [channel] - (While Playwright can operate against the stock Google Chrome and Microsoft Edge browsers available on the machine. In particular, current Playwright version will support Stable and Beta channels of these browsers. See [Google Chrome & Microsoft Edge](https://playwright.dev/docs/browsers/#google-chrome--microsoft-edge).
|
|
89
92
|
* @prop {string[]} [ignoreLog] - An array with console message types that are not logged to debug log. Default value is `['warning', 'log']`. E.g. you can set `[]` to log all messages. See all possible [values](https://playwright.dev/docs/api/class-consolemessage#console-message-type).
|
|
90
93
|
* @prop {boolean} [ignoreHTTPSErrors] - Allows access to untrustworthy pages, e.g. to a page with an expired certificate. Default value is `false`
|
|
94
|
+
* @prop {boolean} [bypassCSP] - bypass Content Security Policy or CSP
|
|
95
|
+
* @prop {boolean} [highlightElement] - highlight the interacting elements
|
|
91
96
|
*/
|
|
92
97
|
const config = {};
|
|
93
98
|
|
|
@@ -125,7 +130,7 @@ const config = {};
|
|
|
125
130
|
*
|
|
126
131
|
* #### Trace Recording Customization
|
|
127
132
|
*
|
|
128
|
-
* Trace recording provides
|
|
133
|
+
* Trace recording provides complete information on test execution and includes DOM snapshots, screenshots, and network requests logged during run.
|
|
129
134
|
* Traces will be saved to `output/trace`
|
|
130
135
|
*
|
|
131
136
|
* * `trace`: enables trace recording for failed tests; trace are saved into `output/trace` folder
|
|
@@ -255,6 +260,22 @@ const config = {};
|
|
|
255
260
|
* }
|
|
256
261
|
* ```
|
|
257
262
|
*
|
|
263
|
+
* * #### Example #9: Launch electron test
|
|
264
|
+
*
|
|
265
|
+
* ```js
|
|
266
|
+
* {
|
|
267
|
+
* helpers: {
|
|
268
|
+
* Playwright: {
|
|
269
|
+
* browser: 'electron',
|
|
270
|
+
* electron: {
|
|
271
|
+
* executablePath: require("electron"),
|
|
272
|
+
* args: [path.join('../', "main.js")],
|
|
273
|
+
* },
|
|
274
|
+
* }
|
|
275
|
+
* },
|
|
276
|
+
* }
|
|
277
|
+
* ```
|
|
278
|
+
*
|
|
258
279
|
* Note: When connecting to remote browser `show` and specific `chrome` options (e.g. `headless` or `devtools`) are ignored.
|
|
259
280
|
*
|
|
260
281
|
* ## Access From Helpers
|
|
@@ -368,15 +389,24 @@ class Playwright extends Helper {
|
|
|
368
389
|
|
|
369
390
|
static _config() {
|
|
370
391
|
return [
|
|
371
|
-
{ name: 'url', message: 'Base url of site to be tested', default: 'http://localhost' },
|
|
372
|
-
{
|
|
373
|
-
name: 'show', message: 'Show browser window', default: true, type: 'confirm',
|
|
374
|
-
},
|
|
375
392
|
{
|
|
376
393
|
name: 'browser',
|
|
377
394
|
message: 'Browser in which testing will be performed. Possible options: chromium, firefox, webkit or electron',
|
|
378
395
|
default: 'chromium',
|
|
379
396
|
},
|
|
397
|
+
{
|
|
398
|
+
name: 'url',
|
|
399
|
+
message: 'Base url of site to be tested',
|
|
400
|
+
default: 'http://localhost',
|
|
401
|
+
when: (answers) => answers.Playwright_browser !== 'electron',
|
|
402
|
+
},
|
|
403
|
+
{
|
|
404
|
+
name: 'show',
|
|
405
|
+
message: 'Show browser window',
|
|
406
|
+
default: true,
|
|
407
|
+
type: 'confirm',
|
|
408
|
+
when: (answers) => answers.Playwright_browser !== 'electron',
|
|
409
|
+
},
|
|
380
410
|
];
|
|
381
411
|
}
|
|
382
412
|
|
|
@@ -938,6 +968,62 @@ class Playwright extends Helper {
|
|
|
938
968
|
return this._waitForAction();
|
|
939
969
|
}
|
|
940
970
|
|
|
971
|
+
/**
|
|
972
|
+
* Calls [focus](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus) on the matching element.
|
|
973
|
+
*
|
|
974
|
+
* Examples:
|
|
975
|
+
*
|
|
976
|
+
* ```js
|
|
977
|
+
* I.dontSee('#add-to-cart-btn');
|
|
978
|
+
* I.focus('#product-tile')
|
|
979
|
+
* I.see('#add-to-cart-bnt');
|
|
980
|
+
* ```
|
|
981
|
+
*
|
|
982
|
+
* @param {CodeceptJS.LocatorOrString} locator field located by label|name|CSS|XPath|strict locator.
|
|
983
|
+
* @param {any} [options] Playwright only: [Additional options](https://playwright.dev/docs/api/class-locator#locator-focus) for available options object as 2nd argument.
|
|
984
|
+
*
|
|
985
|
+
*
|
|
986
|
+
*/
|
|
987
|
+
async focus(locator, options = {}) {
|
|
988
|
+
const els = await this._locate(locator);
|
|
989
|
+
assertElementExists(els, locator, 'Element to focus');
|
|
990
|
+
const el = els[0];
|
|
991
|
+
|
|
992
|
+
await el.focus(options);
|
|
993
|
+
return this._waitForAction();
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
/**
|
|
997
|
+
* Remove focus from a text input, button, etc.
|
|
998
|
+
* Calls [blur](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus) on the element.
|
|
999
|
+
*
|
|
1000
|
+
* Examples:
|
|
1001
|
+
*
|
|
1002
|
+
* ```js
|
|
1003
|
+
* I.blur('.text-area')
|
|
1004
|
+
* ```
|
|
1005
|
+
* ```js
|
|
1006
|
+
* //element `#product-tile` is focused
|
|
1007
|
+
* I.see('#add-to-cart-btn');
|
|
1008
|
+
* I.blur('#product-tile')
|
|
1009
|
+
* I.dontSee('#add-to-cart-btn');
|
|
1010
|
+
* ```
|
|
1011
|
+
*
|
|
1012
|
+
* @param {CodeceptJS.LocatorOrString} locator field located by label|name|CSS|XPath|strict locator.
|
|
1013
|
+
* @param {any} [options] Playwright only: [Additional options](https://playwright.dev/docs/api/class-locator#locator-blur) for available options object as 2nd argument.
|
|
1014
|
+
*
|
|
1015
|
+
*
|
|
1016
|
+
*/
|
|
1017
|
+
async blur(locator, options = {}) {
|
|
1018
|
+
const els = await this._locate(locator);
|
|
1019
|
+
assertElementExists(els, locator, 'Element to blur');
|
|
1020
|
+
// TODO: locator change required after #3677 implementation
|
|
1021
|
+
const elXpath = await getXPathForElement(els[0]);
|
|
1022
|
+
|
|
1023
|
+
await this.page.locator(elXpath).blur(options);
|
|
1024
|
+
return this._waitForAction();
|
|
1025
|
+
}
|
|
1026
|
+
|
|
941
1027
|
/**
|
|
942
1028
|
* Drag an item to a destination element.
|
|
943
1029
|
*
|
|
@@ -949,7 +1035,6 @@ class Playwright extends Helper {
|
|
|
949
1035
|
* @param {LocatorOrString} destElement located by CSS|XPath|strict locator.
|
|
950
1036
|
* ⚠️ returns a _promise_ which is synchronized internally by recorder
|
|
951
1037
|
*
|
|
952
|
-
*
|
|
953
1038
|
* @param {any} [options] [Additional options](https://playwright.dev/docs/api/class-page#page-drag-and-drop) can be passed as 3rd argument.
|
|
954
1039
|
*
|
|
955
1040
|
* ```js
|
|
@@ -957,13 +1042,27 @@ class Playwright extends Helper {
|
|
|
957
1042
|
* I.dragAndDrop('img.src', 'img.dst', { sourcePosition: {x: 10, y: 10} })
|
|
958
1043
|
* ```
|
|
959
1044
|
*
|
|
960
|
-
* >
|
|
1045
|
+
* > When no option is set, custom drag and drop would be used, to use the dragAndDrop API from Playwright, please set options, for example `force: true`
|
|
961
1046
|
*/
|
|
962
|
-
async dragAndDrop(srcElement, destElement, options
|
|
963
|
-
const src = new Locator(srcElement
|
|
964
|
-
const dst = new Locator(destElement
|
|
1047
|
+
async dragAndDrop(srcElement, destElement, options) {
|
|
1048
|
+
const src = new Locator(srcElement);
|
|
1049
|
+
const dst = new Locator(destElement);
|
|
1050
|
+
|
|
1051
|
+
if (options) {
|
|
1052
|
+
return this.page.dragAndDrop(buildLocatorString(src), buildLocatorString(dst), options);
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
const _smallWaitInMs = 600;
|
|
1056
|
+
await this.page.locator(buildLocatorString(src)).hover();
|
|
1057
|
+
await this.page.mouse.down();
|
|
1058
|
+
await this.page.waitForTimeout(_smallWaitInMs);
|
|
1059
|
+
|
|
1060
|
+
const destElBox = await this.page.locator(buildLocatorString(dst)).boundingBox();
|
|
965
1061
|
|
|
966
|
-
|
|
1062
|
+
await this.page.mouse.move(destElBox.x + destElBox.width / 2, destElBox.y + destElBox.height / 2);
|
|
1063
|
+
await this.page.locator(buildLocatorString(dst)).hover({ position: { x: 10, y: 10 } });
|
|
1064
|
+
await this.page.waitForTimeout(_smallWaitInMs);
|
|
1065
|
+
await this.page.mouse.up();
|
|
967
1066
|
}
|
|
968
1067
|
|
|
969
1068
|
/**
|
|
@@ -1461,7 +1560,7 @@ class Playwright extends Helper {
|
|
|
1461
1560
|
* ⚠️ returns a _promise_ which is synchronized internally by recorder
|
|
1462
1561
|
*
|
|
1463
1562
|
*
|
|
1464
|
-
* @param {any} [
|
|
1563
|
+
* @param {any} [options] [Additional options](https://playwright.dev/docs/api/class-page#page-click) for click available as 3rd argument.
|
|
1465
1564
|
*
|
|
1466
1565
|
* Examples:
|
|
1467
1566
|
*
|
|
@@ -1474,8 +1573,8 @@ class Playwright extends Helper {
|
|
|
1474
1573
|
* ```
|
|
1475
1574
|
*
|
|
1476
1575
|
*/
|
|
1477
|
-
async click(locator, context = null,
|
|
1478
|
-
return proceedClick.call(this, locator, context,
|
|
1576
|
+
async click(locator, context = null, options = {}) {
|
|
1577
|
+
return proceedClick.call(this, locator, context, options);
|
|
1479
1578
|
}
|
|
1480
1579
|
|
|
1481
1580
|
/**
|
|
@@ -1811,6 +1910,9 @@ class Playwright extends Helper {
|
|
|
1811
1910
|
*
|
|
1812
1911
|
* // passing in an array
|
|
1813
1912
|
* I.type(['T', 'E', 'X', 'T']);
|
|
1913
|
+
*
|
|
1914
|
+
* // passing a secret
|
|
1915
|
+
* I.type(secret('123456'));
|
|
1814
1916
|
* ```
|
|
1815
1917
|
*
|
|
1816
1918
|
* @param {string|string[]} key or array of keys to type.
|
|
@@ -1820,6 +1922,7 @@ class Playwright extends Helper {
|
|
|
1820
1922
|
*/
|
|
1821
1923
|
async type(keys, delay = null) {
|
|
1822
1924
|
if (!Array.isArray(keys)) {
|
|
1925
|
+
keys = keys.toString();
|
|
1823
1926
|
keys = keys.split('');
|
|
1824
1927
|
}
|
|
1825
1928
|
|
|
@@ -1860,24 +1963,47 @@ class Playwright extends Helper {
|
|
|
1860
1963
|
} else if (editable) {
|
|
1861
1964
|
await this._evaluateHandeInContext(el => el.innerHTML = '', el);
|
|
1862
1965
|
}
|
|
1966
|
+
|
|
1967
|
+
highlightActiveElement.call(this, el, this.page);
|
|
1968
|
+
|
|
1863
1969
|
await el.type(value.toString(), { delay: this.options.pressKeyDelay });
|
|
1970
|
+
|
|
1864
1971
|
return this._waitForAction();
|
|
1865
1972
|
}
|
|
1866
1973
|
|
|
1867
1974
|
/**
|
|
1868
|
-
* Clears
|
|
1869
|
-
*
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1975
|
+
* Clears the text input element: `<input>`, `<textarea>` or `[contenteditable]` .
|
|
1976
|
+
*
|
|
1977
|
+
*
|
|
1978
|
+
* Examples:
|
|
1979
|
+
*
|
|
1980
|
+
* ```js
|
|
1981
|
+
* I.clearField('.text-area')
|
|
1982
|
+
*
|
|
1983
|
+
* // if this doesn't work use force option
|
|
1984
|
+
* I.clearField('#submit', { force: true })
|
|
1985
|
+
* ```
|
|
1986
|
+
* Use `force` to bypass the [actionability](https://playwright.dev/docs/actionability) checks.
|
|
1987
|
+
*
|
|
1988
|
+
* @param {CodeceptJS.LocatorOrString} locator field located by label|name|CSS|XPath|strict locator.
|
|
1989
|
+
* @param {any} [options] [Additional options](https://playwright.dev/docs/api/class-locator#locator-clear) for available options object as 2nd argument.
|
|
1878
1990
|
*/
|
|
1879
|
-
async clearField(
|
|
1880
|
-
|
|
1991
|
+
async clearField(locator, options = {}) {
|
|
1992
|
+
let result;
|
|
1993
|
+
const isNewClearMethodPresent = typeof this.page.locator().clear === 'function';
|
|
1994
|
+
|
|
1995
|
+
if (isNewClearMethodPresent) {
|
|
1996
|
+
const els = await findFields.call(this, locator);
|
|
1997
|
+
assertElementExists(els, locator, 'Field to clear');
|
|
1998
|
+
// TODO: locator change required after #3677 implementation
|
|
1999
|
+
const elXpath = await getXPathForElement(els[0]);
|
|
2000
|
+
|
|
2001
|
+
await this.page.locator(elXpath).clear(options);
|
|
2002
|
+
result = await this._waitForAction();
|
|
2003
|
+
} else {
|
|
2004
|
+
result = await this.fillField(locator, '');
|
|
2005
|
+
}
|
|
2006
|
+
return result;
|
|
1881
2007
|
}
|
|
1882
2008
|
|
|
1883
2009
|
/**
|
|
@@ -1886,6 +2012,8 @@ class Playwright extends Helper {
|
|
|
1886
2012
|
*
|
|
1887
2013
|
* ```js
|
|
1888
2014
|
* I.appendField('#myTextField', 'appended');
|
|
2015
|
+
* // typing secret
|
|
2016
|
+
* I.appendField('password', secret('123456'));
|
|
1889
2017
|
* ```
|
|
1890
2018
|
* @param {CodeceptJS.LocatorOrString} field located by label|name|CSS|XPath|strict locator
|
|
1891
2019
|
* @param {string} value text value to append.
|
|
@@ -1897,8 +2025,9 @@ class Playwright extends Helper {
|
|
|
1897
2025
|
async appendField(field, value) {
|
|
1898
2026
|
const els = await findFields.call(this, field);
|
|
1899
2027
|
assertElementExists(els, field, 'Field');
|
|
2028
|
+
highlightActiveElement.call(this, els[0], this.page);
|
|
1900
2029
|
await els[0].press('End');
|
|
1901
|
-
await els[0].type(value, { delay: this.options.pressKeyDelay });
|
|
2030
|
+
await els[0].type(value.toString(), { delay: this.options.pressKeyDelay });
|
|
1902
2031
|
return this._waitForAction();
|
|
1903
2032
|
}
|
|
1904
2033
|
|
|
@@ -1998,6 +2127,7 @@ class Playwright extends Helper {
|
|
|
1998
2127
|
if (await el.getProperty('tagName').then(t => t.jsonValue()) !== 'SELECT') {
|
|
1999
2128
|
throw new Error('Element is not <select>');
|
|
2000
2129
|
}
|
|
2130
|
+
highlightActiveElement.call(this, el, this.page);
|
|
2001
2131
|
if (!Array.isArray(option)) option = [option];
|
|
2002
2132
|
|
|
2003
2133
|
for (const key in option) {
|
|
@@ -2793,23 +2923,32 @@ class Playwright extends Helper {
|
|
|
2793
2923
|
*/
|
|
2794
2924
|
async saveScreenshot(fileName, fullPage) {
|
|
2795
2925
|
const fullPageOption = fullPage || this.options.fullPageScreenshots;
|
|
2796
|
-
|
|
2926
|
+
let outputFile = screenshotOutputFolder(fileName);
|
|
2797
2927
|
|
|
2798
2928
|
this.debug(`Screenshot is saving to ${outputFile}`);
|
|
2799
2929
|
|
|
2930
|
+
await this.page.screenshot({
|
|
2931
|
+
path: outputFile,
|
|
2932
|
+
fullPage: fullPageOption,
|
|
2933
|
+
type: 'png',
|
|
2934
|
+
});
|
|
2935
|
+
|
|
2800
2936
|
if (this.activeSessionName) {
|
|
2801
|
-
const
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2937
|
+
for (const sessionName in this.sessionPages) {
|
|
2938
|
+
const activeSessionPage = this.sessionPages[sessionName];
|
|
2939
|
+
outputFile = screenshotOutputFolder(`${sessionName}_${fileName}`);
|
|
2940
|
+
|
|
2941
|
+
this.debug(`${sessionName} - Screenshot is saving to ${outputFile}`);
|
|
2942
|
+
|
|
2943
|
+
if (activeSessionPage) {
|
|
2944
|
+
await activeSessionPage.screenshot({
|
|
2945
|
+
path: outputFile,
|
|
2946
|
+
fullPage: fullPageOption,
|
|
2947
|
+
type: 'png',
|
|
2948
|
+
});
|
|
2949
|
+
}
|
|
2809
2950
|
}
|
|
2810
2951
|
}
|
|
2811
|
-
|
|
2812
|
-
return this.page.screenshot({ path: outputFile, fullPage: fullPageOption, type: 'png' });
|
|
2813
2952
|
}
|
|
2814
2953
|
|
|
2815
2954
|
/**
|
|
@@ -2877,7 +3016,7 @@ class Playwright extends Helper {
|
|
|
2877
3016
|
test.artifacts.trace = await saveTraceForContext(this.browserContext, `${test.title}.failed`);
|
|
2878
3017
|
for (const sessionName in this.sessionPages) {
|
|
2879
3018
|
if (!this.sessionPages[sessionName].context) continue;
|
|
2880
|
-
test.artifacts[`trace_${sessionName}`] = await saveTraceForContext(this.sessionPages[sessionName].context
|
|
3019
|
+
test.artifacts[`trace_${sessionName}`] = await saveTraceForContext(this.sessionPages[sessionName].context, `${test.title}_${sessionName}.failed`);
|
|
2881
3020
|
}
|
|
2882
3021
|
}
|
|
2883
3022
|
}
|
|
@@ -2900,7 +3039,7 @@ class Playwright extends Helper {
|
|
|
2900
3039
|
test.artifacts.trace = await saveTraceForContext(this.browserContext, `${test.title}.passed`);
|
|
2901
3040
|
for (const sessionName in this.sessionPages) {
|
|
2902
3041
|
if (!this.sessionPages[sessionName].context) continue;
|
|
2903
|
-
test.artifacts[`trace_${sessionName}`] = await saveTraceForContext(this.sessionPages[sessionName].context
|
|
3042
|
+
test.artifacts[`trace_${sessionName}`] = await saveTraceForContext(this.sessionPages[sessionName].context, `${test.title}_${sessionName}.passed`);
|
|
2904
3043
|
}
|
|
2905
3044
|
}
|
|
2906
3045
|
} else {
|
|
@@ -3386,15 +3525,15 @@ class Playwright extends Helper {
|
|
|
3386
3525
|
*
|
|
3387
3526
|
* See [Playwright's reference](https://playwright.dev/docs/api/class-page?_highlight=waitfornavi#pagewaitfornavigationoptions)
|
|
3388
3527
|
*
|
|
3389
|
-
* @param {*}
|
|
3528
|
+
* @param {*} options
|
|
3390
3529
|
*/
|
|
3391
|
-
async waitForNavigation(
|
|
3392
|
-
|
|
3530
|
+
async waitForNavigation(options = {}) {
|
|
3531
|
+
options = {
|
|
3393
3532
|
timeout: this.options.getPageTimeout,
|
|
3394
3533
|
waitUntil: this.options.waitForNavigation,
|
|
3395
|
-
...
|
|
3534
|
+
...options,
|
|
3396
3535
|
};
|
|
3397
|
-
return this.page.waitForNavigation(
|
|
3536
|
+
return this.page.waitForNavigation(options);
|
|
3398
3537
|
}
|
|
3399
3538
|
|
|
3400
3539
|
async waitUntilExists(locator, sec) {
|
|
@@ -3536,11 +3675,41 @@ function buildLocatorString(locator) {
|
|
|
3536
3675
|
if (locator.isCustom()) {
|
|
3537
3676
|
return `${locator.type}=${locator.value}`;
|
|
3538
3677
|
} if (locator.isXPath()) {
|
|
3539
|
-
// dont rely on heuristics of playwright for figuring out xpath
|
|
3540
3678
|
return `xpath=${locator.value}`;
|
|
3541
3679
|
}
|
|
3542
3680
|
return locator.simplify();
|
|
3543
3681
|
}
|
|
3682
|
+
// TODO: locator change required after #3677 implementation. Temporary solution before migration. Should be deleted after #3677 implementation
|
|
3683
|
+
async function getXPathForElement(elementHandle) {
|
|
3684
|
+
function calculateIndex(node) {
|
|
3685
|
+
let index = 1;
|
|
3686
|
+
let sibling = node.previousElementSibling;
|
|
3687
|
+
while (sibling) {
|
|
3688
|
+
if (sibling.tagName === node.tagName) {
|
|
3689
|
+
index++;
|
|
3690
|
+
}
|
|
3691
|
+
sibling = sibling.previousElementSibling;
|
|
3692
|
+
}
|
|
3693
|
+
return index;
|
|
3694
|
+
}
|
|
3695
|
+
|
|
3696
|
+
function generateXPath(node) {
|
|
3697
|
+
const segments = [];
|
|
3698
|
+
while (node && node.nodeType === Node.ELEMENT_NODE) {
|
|
3699
|
+
if (node.hasAttribute('id')) {
|
|
3700
|
+
segments.unshift(`*[@id="${node.getAttribute('id')}"]`);
|
|
3701
|
+
break;
|
|
3702
|
+
} else {
|
|
3703
|
+
const index = calculateIndex(node);
|
|
3704
|
+
segments.unshift(`${node.localName}[${index}]`);
|
|
3705
|
+
node = node.parentNode;
|
|
3706
|
+
}
|
|
3707
|
+
}
|
|
3708
|
+
return `//${segments.join('/')}`;
|
|
3709
|
+
}
|
|
3710
|
+
|
|
3711
|
+
return elementHandle.evaluate(generateXPath);
|
|
3712
|
+
}
|
|
3544
3713
|
|
|
3545
3714
|
async function findElements(matcher, locator) {
|
|
3546
3715
|
if (locator.react) return findReact(matcher, locator);
|
|
@@ -3574,6 +3743,10 @@ async function proceedClick(locator, context = null, options = {}) {
|
|
|
3574
3743
|
} else {
|
|
3575
3744
|
assertElementExists(els, locator, 'Clickable element');
|
|
3576
3745
|
}
|
|
3746
|
+
|
|
3747
|
+
const element = els[0];
|
|
3748
|
+
highlightActiveElement.call(this, els[0], this.page);
|
|
3749
|
+
|
|
3577
3750
|
/*
|
|
3578
3751
|
using the force true options itself but instead dispatching a click
|
|
3579
3752
|
*/
|
|
@@ -3588,6 +3761,7 @@ async function proceedClick(locator, context = null, options = {}) {
|
|
|
3588
3761
|
promises.push(this.waitForNavigation());
|
|
3589
3762
|
}
|
|
3590
3763
|
promises.push(this._waitForAction());
|
|
3764
|
+
|
|
3591
3765
|
return Promise.all(promises);
|
|
3592
3766
|
}
|
|
3593
3767
|
|
|
@@ -3969,7 +4143,7 @@ async function refreshContextSession() {
|
|
|
3969
4143
|
|
|
3970
4144
|
async function saveVideoForPage(page, name) {
|
|
3971
4145
|
if (!page.video()) return null;
|
|
3972
|
-
const fileName = `${`${global.output_dir}${pathSeparator}videos${pathSeparator}${
|
|
4146
|
+
const fileName = `${`${global.output_dir}${pathSeparator}videos${pathSeparator}${uuidv4()}_${clearString(name)}`.slice(0, 245)}.webm`;
|
|
3973
4147
|
page.video().saveAs(fileName).then(() => {
|
|
3974
4148
|
if (!page) return;
|
|
3975
4149
|
page.video().delete().catch(e => {});
|
|
@@ -3980,7 +4154,13 @@ async function saveVideoForPage(page, name) {
|
|
|
3980
4154
|
async function saveTraceForContext(context, name) {
|
|
3981
4155
|
if (!context) return;
|
|
3982
4156
|
if (!context.tracing) return;
|
|
3983
|
-
const fileName = `${`${global.output_dir}${pathSeparator}trace${pathSeparator}${
|
|
4157
|
+
const fileName = `${`${global.output_dir}${pathSeparator}trace${pathSeparator}${uuidv4()}_${clearString(name)}`.slice(0, 245)}.zip`;
|
|
3984
4158
|
await context.tracing.stop({ path: fileName });
|
|
3985
4159
|
return fileName;
|
|
3986
4160
|
}
|
|
4161
|
+
|
|
4162
|
+
function highlightActiveElement(element, context) {
|
|
4163
|
+
if (!this.options.enableHighlight && !store.debugMode) return;
|
|
4164
|
+
|
|
4165
|
+
highlightElement(element, context);
|
|
4166
|
+
}
|
package/docs/build/Protractor.js
CHANGED
|
@@ -853,6 +853,8 @@ class Protractor extends Helper {
|
|
|
853
853
|
*
|
|
854
854
|
* ```js
|
|
855
855
|
* I.appendField('#myTextField', 'appended');
|
|
856
|
+
* // typing secret
|
|
857
|
+
* I.appendField('password', secret('123456'));
|
|
856
858
|
* ```
|
|
857
859
|
* @param {CodeceptJS.LocatorOrString} field located by label|name|CSS|XPath|strict locator
|
|
858
860
|
* @param {string} value text value to append.
|
|
@@ -862,7 +864,7 @@ class Protractor extends Helper {
|
|
|
862
864
|
async appendField(field, value) {
|
|
863
865
|
const els = await findFields(this.browser, field);
|
|
864
866
|
assertElementExists(els, field, 'Field');
|
|
865
|
-
return els[0].sendKeys(value);
|
|
867
|
+
return els[0].sendKeys(value.toString());
|
|
866
868
|
}
|
|
867
869
|
|
|
868
870
|
/**
|