codeceptjs 3.5.11 â 3.5.12-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/docs/build/Appium.js +35 -35
- package/docs/build/Nightmare.js +50 -50
- package/docs/build/Playwright.js +100 -72
- package/docs/build/Protractor.js +59 -59
- package/docs/build/Puppeteer.js +96 -69
- package/docs/build/TestCafe.js +48 -48
- package/docs/build/WebDriver.js +223 -105
- package/docs/helpers/Playwright.md +15 -0
- package/docs/helpers/Puppeteer.md +15 -0
- package/docs/helpers/WebDriver.md +340 -266
- package/docs/locators.md +9 -1
- package/docs/webapi/waitForNumberOfTabs.mustache +9 -0
- package/docs/webdriver.md +52 -6
- package/lib/command/run-multiple.js +3 -1
- package/lib/command/run-workers.js +32 -1
- package/lib/command/workers/runTests.js +2 -2
- package/lib/css2xpath/js/css_to_xpath.js +20 -0
- package/lib/css2xpath/js/expression.js +23 -0
- package/lib/css2xpath/js/renderer.js +239 -0
- package/lib/helper/Playwright.js +21 -2
- package/lib/helper/Puppeteer.js +18 -0
- package/lib/helper/WebDriver.js +140 -31
- package/lib/locator.js +31 -4
- package/lib/plugin/retryFailedStep.js +5 -1
- package/lib/plugin/retryTo.js +2 -2
- package/package.json +24 -18
- package/typings/index.d.ts +9 -6
- package/typings/promiseBasedTypes.d.ts +84 -1
- package/typings/types.d.ts +102 -2
package/docs/locators.md
CHANGED
|
@@ -163,12 +163,20 @@ locate('form').withDescendant('select');
|
|
|
163
163
|
|
|
164
164
|
#### withText
|
|
165
165
|
|
|
166
|
-
|
|
166
|
+
Find an element containing a text
|
|
167
167
|
|
|
168
168
|
```js
|
|
169
169
|
locate('span').withText('Warning');
|
|
170
170
|
```
|
|
171
171
|
|
|
172
|
+
#### withTextEquals
|
|
173
|
+
|
|
174
|
+
Find an element with exact text
|
|
175
|
+
|
|
176
|
+
```js
|
|
177
|
+
locate('button').withTextEquals('Add');
|
|
178
|
+
```
|
|
179
|
+
|
|
172
180
|
#### first
|
|
173
181
|
|
|
174
182
|
Get first element:
|
package/docs/webdriver.md
CHANGED
|
@@ -7,11 +7,11 @@ title: Testing with WebDriver
|
|
|
7
7
|
|
|
8
8
|
How does your client, manager, or tester, or any other non-technical person, know your web application is working? By opening the browser, accessing a site, clicking on links, filling in the forms, and actually seeing the content on a web page.
|
|
9
9
|
|
|
10
|
-
End
|
|
10
|
+
End-to-End tests can cover standard but complex scenarios from a user's perspective. With e2e tests you can be confident that users, following all defined scenarios, won't get errors. We check **functionality of application and a user interface** (UI) as well.
|
|
11
11
|
|
|
12
12
|
## What is Selenium WebDriver
|
|
13
13
|
|
|
14
|
-
The standard and proved way to run browser test automation over years is Selenium WebDriver. Over years this technology was
|
|
14
|
+
The standard and proved way to run browser test automation over years is Selenium WebDriver. Over years this technology was standardized and works over all popular browsers and operating systems. There are cloud services like SauceLabs or BrowserStack which allow executing such browsers in the cloud. The superset of WebDriver protocol is also used to test [native and hybrid mobile applications](/mobile).
|
|
15
15
|
|
|
16
16
|
Let's clarify the terms:
|
|
17
17
|
|
|
@@ -53,14 +53,60 @@ exports.config = {
|
|
|
53
53
|
|
|
54
54
|
> â It is not recommended to use wdio plugin & selenium-standalone when running tests in parallel. Consider **switching to Selenoid** if you need parallel run or using cloud services.
|
|
55
55
|
|
|
56
|
+
đŠī¸ With the release of WebdriverIO version v8.14.0, and onwards, all driver management hassles are now a thing of the past đ. Read more [here](https://webdriver.io/blog/2023/07/31/driver-management/).
|
|
57
|
+
One of the significant advantages of this update is that you can now get rid of any driver services you previously had to manage, such as
|
|
58
|
+
`wdio-chromedriver-service`, `wdio-geckodriver-service`, `wdio-edgedriver-service`, `wdio-safaridriver-service`, and even `@wdio/selenium-standalone-service`.
|
|
59
|
+
|
|
60
|
+
For those who require custom driver options, fear not; WebDriver Helper allows you to pass in driver options through custom WebDriver configuration.
|
|
61
|
+
If you have a custom grid, use a cloud service, or prefer to run your own driver, there's no need to worry since WebDriver Helper will only start a driver when there are no other connection information settings like hostname or port specified.
|
|
62
|
+
|
|
63
|
+
Example:
|
|
64
|
+
|
|
65
|
+
```js
|
|
66
|
+
{
|
|
67
|
+
helpers: {
|
|
68
|
+
WebDriver : {
|
|
69
|
+
smartWait: 5000,
|
|
70
|
+
browser: "chrome",
|
|
71
|
+
restart: false,
|
|
72
|
+
windowSize: "maximize",
|
|
73
|
+
timeouts: {
|
|
74
|
+
"script": 60000,
|
|
75
|
+
"page load": 10000
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Testing Chrome locally is now more convenient than ever. You can define a browser channel, and WebDriver Helper will take care of downloading the specified browser version for you.
|
|
83
|
+
For example:
|
|
84
|
+
|
|
85
|
+
```js
|
|
86
|
+
{
|
|
87
|
+
helpers: {
|
|
88
|
+
WebDriver : {
|
|
89
|
+
smartWait: 5000,
|
|
90
|
+
browser: "chrome",
|
|
91
|
+
browserVersion: '116.0.5793.0', // or 'stable', 'beta', 'dev' or 'canary'
|
|
92
|
+
restart: false,
|
|
93
|
+
windowSize: "maximize",
|
|
94
|
+
timeouts: {
|
|
95
|
+
"script": 60000,
|
|
96
|
+
"page load": 10000
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
```
|
|
56
102
|
|
|
57
103
|
## Configuring WebDriver
|
|
58
104
|
|
|
59
105
|
WebDriver can be configured to run browser tests in window, headlessly, on a remote server or in a cloud.
|
|
60
106
|
|
|
61
|
-
> By default CodeceptJS is already configured to run WebDriver tests locally with Chrome or Firefox. If you just need to start running tests - proceed to the next chapter.
|
|
107
|
+
> By default, CodeceptJS is already configured to run WebDriver tests locally with Chrome or Firefox. If you just need to start running tests - proceed to the next chapter.
|
|
62
108
|
|
|
63
|
-
Configuration for WebDriver should be provided inside `codecept.conf.js` file under `helpers: WebDriver` section:
|
|
109
|
+
Configuration for WebDriver should be provided inside `codecept.conf.(js|ts)` file under `helpers: WebDriver` section:
|
|
64
110
|
|
|
65
111
|
```js
|
|
66
112
|
helpers: {
|
|
@@ -80,7 +126,7 @@ Configuration for WebDriver should be provided inside `codecept.conf.js` file un
|
|
|
80
126
|
}
|
|
81
127
|
```
|
|
82
128
|
|
|
83
|
-
By default CodeceptJS runs tests in the same browser window but clears cookies and local storage after each test. This behavior can be changed with these options:
|
|
129
|
+
By default, CodeceptJS runs tests in the same browser window but clears cookies and local storage after each test. This behavior can be changed with these options:
|
|
84
130
|
|
|
85
131
|
```js
|
|
86
132
|
// change to true to restart browser between tests
|
|
@@ -152,7 +198,7 @@ desiredCapabilities: {
|
|
|
152
198
|
|
|
153
199
|
WebDriver protocol works over HTTP, so you need to have a Selenium Server to be running or any other service that will launch a browser for you. That's why you may need to specify `host`, `port`, `protocol`, and `path` parameters.
|
|
154
200
|
|
|
155
|
-
By default, those parameters are set to connect to local Selenium Server but they should be changed if you want to run tests via [Cloud Providers](/helpers/WebDriver#cloud-providers). You may also need `user` and `key` parameters to authenticate on cloud service.
|
|
201
|
+
By default, those parameters are set to connect to local Selenium Server, but they should be changed if you want to run tests via [Cloud Providers](/helpers/WebDriver#cloud-providers). You may also need `user` and `key` parameters to authenticate on cloud service.
|
|
156
202
|
|
|
157
203
|
There are also [browser and platform specific capabilities](https://github.com/SeleniumHQ/selenium/wiki/DesiredCapabilities). Services like SauceLabs, BrowserStack or browser vendors can provide their own specific capabilities for more tuning.
|
|
158
204
|
|
|
@@ -28,7 +28,9 @@ let processesDone;
|
|
|
28
28
|
|
|
29
29
|
module.exports = async function (selectedRuns, options) {
|
|
30
30
|
// registering options globally to use in config
|
|
31
|
-
|
|
31
|
+
if (options.profile) {
|
|
32
|
+
process.env.profile = options.profile;
|
|
33
|
+
}
|
|
32
34
|
const configFile = options.config;
|
|
33
35
|
|
|
34
36
|
const testRoot = getTestRoot(configFile);
|
|
@@ -11,6 +11,7 @@ module.exports = async function (workerCount, selectedRuns, options) {
|
|
|
11
11
|
const passedTestArr = [];
|
|
12
12
|
const failedTestArr = [];
|
|
13
13
|
const skippedTestArr = [];
|
|
14
|
+
const stepArr = [];
|
|
14
15
|
|
|
15
16
|
const { config: testConfig, override = '' } = options;
|
|
16
17
|
const overrideConfigs = tryOrDefault(() => JSON.parse(override), {});
|
|
@@ -36,6 +37,14 @@ module.exports = async function (workerCount, selectedRuns, options) {
|
|
|
36
37
|
suiteArr.push(suite);
|
|
37
38
|
});
|
|
38
39
|
|
|
40
|
+
workers.on(event.step.passed, (step) => {
|
|
41
|
+
stepArr.push(step);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
workers.on(event.step.failed, (step) => {
|
|
45
|
+
stepArr.push(step);
|
|
46
|
+
});
|
|
47
|
+
|
|
39
48
|
workers.on(event.test.failed, (test) => {
|
|
40
49
|
failedTestArr.push(test);
|
|
41
50
|
output.test.failed(test);
|
|
@@ -48,11 +57,33 @@ module.exports = async function (workerCount, selectedRuns, options) {
|
|
|
48
57
|
|
|
49
58
|
workers.on(event.test.skipped, (test) => {
|
|
50
59
|
skippedTestArr.push(test);
|
|
51
|
-
output.test.
|
|
60
|
+
output.test.skipped(test);
|
|
52
61
|
});
|
|
53
62
|
|
|
54
63
|
workers.on(event.all.result, () => {
|
|
55
64
|
// expose test stats after all workers finished their execution
|
|
65
|
+
function addStepsToTest(test, stepArr) {
|
|
66
|
+
stepArr.test.steps.forEach(step => {
|
|
67
|
+
if (test.steps.length === 0) {
|
|
68
|
+
test.steps.push(step);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
stepArr.forEach(step => {
|
|
74
|
+
passedTestArr.forEach(test => {
|
|
75
|
+
if (step.test.title === test.title) {
|
|
76
|
+
addStepsToTest(test, step);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
failedTestArr.forEach(test => {
|
|
81
|
+
if (step.test.title === test.title) {
|
|
82
|
+
addStepsToTest(test, step);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
56
87
|
event.dispatcher.emit(event.workers.result, {
|
|
57
88
|
suites: suiteArr,
|
|
58
89
|
tests: {
|
|
@@ -132,7 +132,7 @@ function initializeListeners() {
|
|
|
132
132
|
duration: test.duration || 0,
|
|
133
133
|
err,
|
|
134
134
|
parent,
|
|
135
|
-
steps: test.steps ? simplifyStepsInTestObject(test.steps, err) : [],
|
|
135
|
+
steps: test.steps && test.steps.length > 0 ? simplifyStepsInTestObject(test.steps, err) : [],
|
|
136
136
|
};
|
|
137
137
|
}
|
|
138
138
|
|
|
@@ -161,7 +161,7 @@ function initializeListeners() {
|
|
|
161
161
|
actor: step.actor,
|
|
162
162
|
name: step.name,
|
|
163
163
|
status: step.status,
|
|
164
|
-
|
|
164
|
+
args: _args,
|
|
165
165
|
startedAt: step.startedAt,
|
|
166
166
|
startTime: step.startTime,
|
|
167
167
|
endTime: step.endTime,
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
(function() {
|
|
2
|
+
var self = this;
|
|
3
|
+
var parser, xpathBuilder, Expression, parse, convertToXpath;
|
|
4
|
+
parser = require("bo-selector").parser;
|
|
5
|
+
xpathBuilder = require("xpath-builder").dsl();
|
|
6
|
+
Expression = require("./expression");
|
|
7
|
+
parser.yy.create = function(data) {
|
|
8
|
+
var self = this;
|
|
9
|
+
return new Expression(data);
|
|
10
|
+
};
|
|
11
|
+
parse = function(selector) {
|
|
12
|
+
return parser.parse(selector).render(xpathBuilder, "descendant");
|
|
13
|
+
};
|
|
14
|
+
convertToXpath = function(selector) {
|
|
15
|
+
return parse(selector).toXPath();
|
|
16
|
+
};
|
|
17
|
+
convertToXpath.parse = parse;
|
|
18
|
+
convertToXpath.xPathBuilder = xpathBuilder;
|
|
19
|
+
module.exports = convertToXpath;
|
|
20
|
+
}).call(this);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
(function() {
|
|
2
|
+
var self = this;
|
|
3
|
+
var Renderer, Expression, extend;
|
|
4
|
+
Renderer = require("./renderer");
|
|
5
|
+
Expression = function(data) {
|
|
6
|
+
extend(this, data);
|
|
7
|
+
return this;
|
|
8
|
+
};
|
|
9
|
+
Expression.prototype.render = function(xpath, combinator) {
|
|
10
|
+
var self = this;
|
|
11
|
+
return new Renderer().render(self, xpath, combinator);
|
|
12
|
+
};
|
|
13
|
+
extend = function(o, d) {
|
|
14
|
+
var k;
|
|
15
|
+
for (k in d) {
|
|
16
|
+
(function(k) {
|
|
17
|
+
o[k] = d[k];
|
|
18
|
+
})(k);
|
|
19
|
+
}
|
|
20
|
+
return void 0;
|
|
21
|
+
};
|
|
22
|
+
module.exports = Expression;
|
|
23
|
+
}).call(this);
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
(function() {
|
|
2
|
+
var self = this;
|
|
3
|
+
var xpathBuilder, Renderer;
|
|
4
|
+
xpathBuilder = require("xpath-builder").dsl();
|
|
5
|
+
Renderer = function() {
|
|
6
|
+
return this;
|
|
7
|
+
};
|
|
8
|
+
Renderer.prototype = {
|
|
9
|
+
render: function(node, xpath, combinator) {
|
|
10
|
+
var self = this;
|
|
11
|
+
var fn;
|
|
12
|
+
fn = self[node.type];
|
|
13
|
+
if (!fn) {
|
|
14
|
+
throw new Error("No renderer for '" + node.type + "'");
|
|
15
|
+
}
|
|
16
|
+
return fn.call(self, node, xpath, combinator);
|
|
17
|
+
},
|
|
18
|
+
selector_list: function(node, xpath, combinator) {
|
|
19
|
+
var self = this;
|
|
20
|
+
var x, i;
|
|
21
|
+
x = self.render(node.selectors[0], xpath, combinator);
|
|
22
|
+
for (i = 1; i < node.selectors.length; ++i) {
|
|
23
|
+
x = x.union(self.render(node.selectors[i], xpath, combinator));
|
|
24
|
+
}
|
|
25
|
+
return x;
|
|
26
|
+
},
|
|
27
|
+
constraint_list: function(node, xpath, combinator) {
|
|
28
|
+
var self = this;
|
|
29
|
+
return self.element(node, xpath, combinator);
|
|
30
|
+
},
|
|
31
|
+
element: function(node, xpath, combinator) {
|
|
32
|
+
var self = this;
|
|
33
|
+
return self.applyConstraints(node, xpath[combinator].call(xpath, node.name || "*"));
|
|
34
|
+
},
|
|
35
|
+
combinator_selector: function(node, xpath, combinator) {
|
|
36
|
+
var self = this;
|
|
37
|
+
var left;
|
|
38
|
+
left = self.render(node.left, xpath, combinator);
|
|
39
|
+
return self.render(node.right, left, node.combinator);
|
|
40
|
+
},
|
|
41
|
+
immediate_child: function(node, xpath) {
|
|
42
|
+
var self = this;
|
|
43
|
+
return self.render(node.child, xpath, "child");
|
|
44
|
+
},
|
|
45
|
+
pseudo_func: function(node, xpath) {
|
|
46
|
+
var self = this;
|
|
47
|
+
var fn;
|
|
48
|
+
fn = self[node.func.name.replace(/-/g, "_")];
|
|
49
|
+
if (fn) {
|
|
50
|
+
return fn.call(self, node, xpath);
|
|
51
|
+
} else {
|
|
52
|
+
throw new Error("Unsupported pseudo function :" + node.func.name + "()");
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
pseudo_class: function(node, xpath) {
|
|
56
|
+
var self = this;
|
|
57
|
+
var fn;
|
|
58
|
+
fn = self[node.name.replace(/-/g, "_")];
|
|
59
|
+
if (fn) {
|
|
60
|
+
return fn.call(self, node, xpath);
|
|
61
|
+
} else {
|
|
62
|
+
throw new Error("Unsupported pseudo class :" + node.name);
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
has: function(node, xpath) {
|
|
66
|
+
var self = this;
|
|
67
|
+
return self.render(node.func.args, xpathBuilder, "descendant");
|
|
68
|
+
},
|
|
69
|
+
not: function(node, xpath) {
|
|
70
|
+
var self = this;
|
|
71
|
+
var firstChild, childType;
|
|
72
|
+
firstChild = node.func.args.selectors[0];
|
|
73
|
+
childType = firstChild.type;
|
|
74
|
+
if (childType === "constraint_list") {
|
|
75
|
+
return self.combineConstraints(firstChild, xpath).inverse();
|
|
76
|
+
} else {
|
|
77
|
+
return self.matchesSelectorList(node.func.args, xpath).inverse();
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
nth_child: function(node, xpath) {
|
|
81
|
+
var self = this;
|
|
82
|
+
return xpath.nthChild(Number(node.func.args));
|
|
83
|
+
},
|
|
84
|
+
first_child: function(node, xpath) {
|
|
85
|
+
var self = this;
|
|
86
|
+
return xpath.firstChild();
|
|
87
|
+
},
|
|
88
|
+
last_child: function(node, xpath) {
|
|
89
|
+
var self = this;
|
|
90
|
+
return xpath.lastChild();
|
|
91
|
+
},
|
|
92
|
+
nth_last_child: function(node, xpath) {
|
|
93
|
+
var self = this;
|
|
94
|
+
return xpath.nthLastChild(Number(node.func.args));
|
|
95
|
+
},
|
|
96
|
+
only_child: function(node, xpath) {
|
|
97
|
+
var self = this;
|
|
98
|
+
return xpath.onlyChild();
|
|
99
|
+
},
|
|
100
|
+
only_of_type: function(node, xpath) {
|
|
101
|
+
var self = this;
|
|
102
|
+
return xpath.onlyOfType();
|
|
103
|
+
},
|
|
104
|
+
nth_of_type: function(node, xpath) {
|
|
105
|
+
var self = this;
|
|
106
|
+
var type;
|
|
107
|
+
type = node.func.args.type;
|
|
108
|
+
if (type === "odd") {
|
|
109
|
+
return xpath.nthOfTypeOdd();
|
|
110
|
+
} else if (type === "even") {
|
|
111
|
+
return xpath.nthOfTypeEven();
|
|
112
|
+
} else if (type === "an") {
|
|
113
|
+
return xpath.nthOfTypeMod(Number(node.func.args.a));
|
|
114
|
+
} else if (type === "n_plus_b") {
|
|
115
|
+
return xpath.nthOfTypeMod(1, Number(node.func.args.b));
|
|
116
|
+
} else if (type === "an_plus_b") {
|
|
117
|
+
return xpath.nthOfTypeMod(Number(node.func.args.a), Number(node.func.args.b));
|
|
118
|
+
} else {
|
|
119
|
+
return xpath.nthOfType(Number(node.func.args));
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
nth_last_of_type: function(node, xpath) {
|
|
123
|
+
var self = this;
|
|
124
|
+
var type;
|
|
125
|
+
type = node.func.args.type;
|
|
126
|
+
if (type === "odd") {
|
|
127
|
+
return xpath.nthLastOfTypeOdd();
|
|
128
|
+
} else if (type === "even") {
|
|
129
|
+
return xpath.nthLastOfTypeEven();
|
|
130
|
+
} else if (type === "an") {
|
|
131
|
+
return xpath.nthLastOfTypeMod(Number(node.func.args.a));
|
|
132
|
+
} else if (type === "n_plus_b") {
|
|
133
|
+
return xpath.nthLastOfTypeMod(1, Number(node.func.args.b));
|
|
134
|
+
} else if (type === "an_plus_b") {
|
|
135
|
+
return xpath.nthLastOfTypeMod(Number(node.func.args.a), Number(node.func.args.b));
|
|
136
|
+
} else {
|
|
137
|
+
return xpath.nthLastOfType(Number(node.func.args));
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
last_of_type: function(node, xpath) {
|
|
141
|
+
var self = this;
|
|
142
|
+
return xpath.lastOfType();
|
|
143
|
+
},
|
|
144
|
+
empty: function(node, xpath) {
|
|
145
|
+
var self = this;
|
|
146
|
+
return xpath.empty();
|
|
147
|
+
},
|
|
148
|
+
has_attribute: function(node, xpath) {
|
|
149
|
+
var self = this;
|
|
150
|
+
return xpathBuilder.attr(node.name);
|
|
151
|
+
},
|
|
152
|
+
attribute_equals: function(node, xpath) {
|
|
153
|
+
var self = this;
|
|
154
|
+
return xpathBuilder.attr(node.name).equals(node.value);
|
|
155
|
+
},
|
|
156
|
+
attribute_contains: function(node, xpath) {
|
|
157
|
+
var self = this;
|
|
158
|
+
return xpathBuilder.attr(node.name).contains(node.value);
|
|
159
|
+
},
|
|
160
|
+
attribute_contains_word: function(node, xpath) {
|
|
161
|
+
var self = this;
|
|
162
|
+
return xpath.concat(" ", xpathBuilder.attr(node.name).normalize(), " ").contains(" " + node.value + " ");
|
|
163
|
+
},
|
|
164
|
+
attribute_contains_prefix: function(node) {
|
|
165
|
+
var self = this;
|
|
166
|
+
return xpathBuilder.attr(node.name).startsWith(node.value).or(xpathBuilder.attr(node.name).startsWith(node.value + "-"));
|
|
167
|
+
},
|
|
168
|
+
attribute_starts_with: function(node, xpath) {
|
|
169
|
+
var self = this;
|
|
170
|
+
return xpathBuilder.attr(node.name).startsWith(node.value);
|
|
171
|
+
},
|
|
172
|
+
attribute_ends_with: function(node) {
|
|
173
|
+
var self = this;
|
|
174
|
+
return xpathBuilder.attr(node.name).endsWith(node.value);
|
|
175
|
+
},
|
|
176
|
+
"class": function(node) {
|
|
177
|
+
var self = this;
|
|
178
|
+
return self.attribute_contains_word({
|
|
179
|
+
name: "class",
|
|
180
|
+
value: node.name
|
|
181
|
+
}, xpathBuilder);
|
|
182
|
+
},
|
|
183
|
+
id: function(node) {
|
|
184
|
+
var self = this;
|
|
185
|
+
return xpathBuilder.attr("id").equals(node.name);
|
|
186
|
+
},
|
|
187
|
+
previous_sibling: function(node, xpath, combinator) {
|
|
188
|
+
var self = this;
|
|
189
|
+
var left;
|
|
190
|
+
left = self.render(node.left, xpath, combinator);
|
|
191
|
+
return self.applyConstraints(node.right, left.axis("following-sibling", node.right.name));
|
|
192
|
+
},
|
|
193
|
+
adjacent_sibling: function(node, xpath, combinator) {
|
|
194
|
+
var self = this;
|
|
195
|
+
var left;
|
|
196
|
+
left = self.render(node.left, xpath, combinator);
|
|
197
|
+
return self.applyConstraints(node.right, left.axis("following-sibling::*[1]/self", node.right.name));
|
|
198
|
+
},
|
|
199
|
+
first_of_type: function(node, xpath) {
|
|
200
|
+
var self = this;
|
|
201
|
+
return xpath.firstOfType();
|
|
202
|
+
},
|
|
203
|
+
matchesSelectorList: function(node, xpath) {
|
|
204
|
+
var self = this;
|
|
205
|
+
var condition, i;
|
|
206
|
+
if (node.selectors.length > 0) {
|
|
207
|
+
condition = self.matchesSelector(node.selectors[0], xpathBuilder);
|
|
208
|
+
for (i = 1; i < node.selectors.length; ++i) {
|
|
209
|
+
condition = condition.or(self.matchesSelector(node.selectors[i], xpathBuilder));
|
|
210
|
+
}
|
|
211
|
+
return condition;
|
|
212
|
+
} else {
|
|
213
|
+
return xpath;
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
matchesSelector: function(node, xpath) {
|
|
217
|
+
var self = this;
|
|
218
|
+
return xpath.name().equals(node.name);
|
|
219
|
+
},
|
|
220
|
+
combineConstraints: function(node, xpath) {
|
|
221
|
+
var self = this;
|
|
222
|
+
var condition, i;
|
|
223
|
+
condition = self.render(node.constraints[0], xpath);
|
|
224
|
+
for (i = 1; i < node.constraints.length; ++i) {
|
|
225
|
+
condition = condition.and(self.render(node.constraints[i], condition));
|
|
226
|
+
}
|
|
227
|
+
return condition;
|
|
228
|
+
},
|
|
229
|
+
applyConstraints: function(node, xpath) {
|
|
230
|
+
var self = this;
|
|
231
|
+
if (node.constraints.length > 0) {
|
|
232
|
+
return xpath.where(self.combineConstraints(node, xpath));
|
|
233
|
+
} else {
|
|
234
|
+
return xpath;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
module.exports = Renderer;
|
|
239
|
+
}).call(this);
|
package/lib/helper/Playwright.js
CHANGED
|
@@ -517,8 +517,9 @@ class Playwright extends Helper {
|
|
|
517
517
|
if (this.options.userAgent) contextOptions.userAgent = this.options.userAgent;
|
|
518
518
|
if (this.options.locale) contextOptions.locale = this.options.locale;
|
|
519
519
|
if (this.options.colorScheme) contextOptions.colorScheme = this.options.colorScheme;
|
|
520
|
+
this.contextOptions = contextOptions;
|
|
520
521
|
if (!this.browserContext || !restartsSession()) {
|
|
521
|
-
this.browserContext = await this.browser.newContext(contextOptions); // Adding the HTTPSError ignore in the context so that we can ignore those errors
|
|
522
|
+
this.browserContext = await this.browser.newContext(this.contextOptions); // Adding the HTTPSError ignore in the context so that we can ignore those errors
|
|
522
523
|
}
|
|
523
524
|
}
|
|
524
525
|
|
|
@@ -606,7 +607,7 @@ class Playwright extends Helper {
|
|
|
606
607
|
page = await browser.firstWindow();
|
|
607
608
|
} else {
|
|
608
609
|
try {
|
|
609
|
-
browserContext = await this.browser.newContext(Object.assign(this.
|
|
610
|
+
browserContext = await this.browser.newContext(Object.assign(this.contextOptions, config));
|
|
610
611
|
page = await browserContext.newPage();
|
|
611
612
|
} catch (e) {
|
|
612
613
|
if (this.playwrightOptions.userDataDir) {
|
|
@@ -2583,6 +2584,24 @@ class Playwright extends Helper {
|
|
|
2583
2584
|
});
|
|
2584
2585
|
}
|
|
2585
2586
|
|
|
2587
|
+
/**
|
|
2588
|
+
* {{> waitForNumberOfTabs }}
|
|
2589
|
+
*/
|
|
2590
|
+
async waitForNumberOfTabs(expectedTabs, sec) {
|
|
2591
|
+
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout;
|
|
2592
|
+
let currentTabs;
|
|
2593
|
+
let count = 0;
|
|
2594
|
+
|
|
2595
|
+
do {
|
|
2596
|
+
currentTabs = await this.grabNumberOfOpenTabs();
|
|
2597
|
+
await this.wait(1);
|
|
2598
|
+
count += 1000;
|
|
2599
|
+
if (currentTabs >= expectedTabs) return;
|
|
2600
|
+
} while (count <= waitTimeout);
|
|
2601
|
+
|
|
2602
|
+
throw new Error(`Expected ${expectedTabs} tabs are not met after ${waitTimeout / 1000} sec.`);
|
|
2603
|
+
}
|
|
2604
|
+
|
|
2586
2605
|
async _getContext() {
|
|
2587
2606
|
if (this.context && this.context.constructor.name === 'FrameLocator') {
|
|
2588
2607
|
return this.context;
|
package/lib/helper/Puppeteer.js
CHANGED
|
@@ -2151,6 +2151,24 @@ class Puppeteer extends Helper {
|
|
|
2151
2151
|
});
|
|
2152
2152
|
}
|
|
2153
2153
|
|
|
2154
|
+
/**
|
|
2155
|
+
* {{> waitForNumberOfTabs }}
|
|
2156
|
+
*/
|
|
2157
|
+
async waitForNumberOfTabs(expectedTabs, sec) {
|
|
2158
|
+
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout;
|
|
2159
|
+
let currentTabs;
|
|
2160
|
+
let count = 0;
|
|
2161
|
+
|
|
2162
|
+
do {
|
|
2163
|
+
currentTabs = await this.grabNumberOfOpenTabs();
|
|
2164
|
+
await this.wait(1);
|
|
2165
|
+
count += 1000;
|
|
2166
|
+
if (currentTabs >= expectedTabs) return;
|
|
2167
|
+
} while (count <= waitTimeout);
|
|
2168
|
+
|
|
2169
|
+
throw new Error(`Expected ${expectedTabs} tabs are not met after ${waitTimeout / 1000} sec.`);
|
|
2170
|
+
}
|
|
2171
|
+
|
|
2154
2172
|
async _getContext() {
|
|
2155
2173
|
if (this.context && this.context.constructor.name === 'Frame') {
|
|
2156
2174
|
return this.context;
|