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/docs/locators.md CHANGED
@@ -163,12 +163,20 @@ locate('form').withDescendant('select');
163
163
 
164
164
  #### withText
165
165
 
166
- Finds element containing a text
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:
@@ -0,0 +1,9 @@
1
+ Waits for number of tabs.
2
+
3
+ ```js
4
+ I.waitForNumberOfTabs(2);
5
+ ```
6
+
7
+ @param {number} expectedTabs expecting the number of tabs.
8
+ @param {number} sec number of secs to wait.
9
+ @returns {void} automatically synchronized promise through #recorder
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 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.
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 standartized 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).
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
- process.env.profile = options.profile;
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.passed(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
- agrs: _args,
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);
@@ -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.options, config));
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;
@@ -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;