@camperaid/watest 2.6.2 → 2.6.3

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/.watestrc.js CHANGED
@@ -54,6 +54,12 @@ const cfg = {
54
54
  */
55
55
  webdrivers: process.env.WATEST_WEBDRIVERS,
56
56
 
57
+ /**
58
+ * Project-defined webdriver aliases. JSON object mapping alias names to
59
+ * { browser, isMobile?, chrome?, windowWidth?, windowHeight? }.
60
+ */
61
+ webdriver_aliases: process.env.WATEST_WEBDRIVER_ALIASES,
62
+
57
63
  /**
58
64
  * Logger module.
59
65
  */
package/README.md CHANGED
@@ -121,6 +121,7 @@ WATEST_TMP_DIR=/tmp # Temporary files directory
121
121
 
122
122
  # WebDriver
123
123
  WATEST_WEBDRIVERS='["chrome","firefox"]' # Browser list (JSON array)
124
+ WATEST_WEBDRIVER_ALIASES='{"chrome-mobile":{"browser":"chrome","isMobile":true,"chrome":{"mobileEmulation":{"deviceName":"iPhone 7"}}}}'
124
125
  WATEST_WEBDRIVER_HEADLESS=true # Run browsers headless
125
126
  WATEST_WEBDRIVER_LOGLEVEL=info # WebDriver log level
126
127
  WATEST_WEBDRIVER_WINDOW_WIDTH=1280 # Browser window width
@@ -134,12 +135,38 @@ WATEST_IGNORE_PATTERN=_browser\.js$ # Regex for files to skip
134
135
 
135
136
  ### Webdrivers
136
137
 
137
- Built-in webdriver configurations:
138
+ Built-in browsers:
138
139
 
139
140
  - `chrome` - Chrome browser
140
- - `chrome-mobile` - Chrome with iPhone emulation
141
141
  - `firefox` - Firefox browser
142
142
  - `safari` - Safari browser
143
+ - `edge` - Edge browser
144
+
145
+ Project-defined aliases go in `.watestrc.js` as `webdriver_aliases` (or
146
+ `WATEST_WEBDRIVER_ALIASES` JSON env var). Each alias maps a name to a base
147
+ browser plus overrides:
148
+
149
+ ```js
150
+ webdriver_aliases: {
151
+ 'chrome-mobile': {
152
+ browser: 'chrome',
153
+ isMobile: true,
154
+ chrome: {
155
+ mobileEmulation: { deviceName: 'iPhone 7' },
156
+ },
157
+ },
158
+ 'chrome-iphone10': {
159
+ browser: 'chrome',
160
+ isMobile: true,
161
+ chrome: {
162
+ mobileEmulation: { deviceName: 'iPhone X' },
163
+ },
164
+ },
165
+ },
166
+ ```
167
+
168
+ Supported alias fields: `browser` (required), `isMobile`, `browserLoggingOn`,
169
+ `chrome.mobileEmulation`, `windowWidth`, `windowHeight`.
143
170
 
144
171
  ## CLI Options
145
172
 
package/core/series.js CHANGED
@@ -692,6 +692,9 @@ class Series {
692
692
  // Set a webdiver on stack if not empty.
693
693
  if (webdriver) {
694
694
  settings.webdriver = webdriver;
695
+ process.env.WATEST_WEBDRIVER = webdriver;
696
+ } else {
697
+ delete process.env.WATEST_WEBDRIVER;
695
698
  }
696
699
 
697
700
  // Subtests.
package/core/settings.js CHANGED
@@ -1,5 +1,14 @@
1
1
  import path from 'path';
2
2
 
3
+ const baseBrowsers = ['chrome', 'firefox', 'safari', 'edge'];
4
+
5
+ const baseBrowserDefaults = {
6
+ chrome: { chrome: true, browserLoggingOn: true },
7
+ firefox: { firefox: true },
8
+ safari: { safari: true },
9
+ edge: { edge: true },
10
+ };
11
+
3
12
  class Settings {
4
13
  constructor() {
5
14
  this.log_dir = '';
@@ -8,6 +17,11 @@ class Settings {
8
17
  this.logger = null;
9
18
  this.servicer = null;
10
19
  this.silent = false;
20
+ this.webdriver_aliases = {};
21
+ }
22
+
23
+ get baseBrowsers() {
24
+ return baseBrowsers;
11
25
  }
12
26
 
13
27
  async initialize(options = {}) {
@@ -94,7 +108,128 @@ class Settings {
94
108
  }
95
109
  }
96
110
 
111
+ parseJsonObject(value, label) {
112
+ if (value == null || value === '') {
113
+ return null;
114
+ }
115
+ if (typeof value == 'object') {
116
+ return value;
117
+ }
118
+ if (typeof value == 'string') {
119
+ try {
120
+ return JSON.parse(value);
121
+ } catch (e) {
122
+ console.error(`Settings: failed to parse ${label} '${value}'`, e);
123
+ }
124
+ }
125
+ return null;
126
+ }
127
+
128
+ setupWebdriverAliases() {
129
+ this.webdriver_aliases = {};
130
+
131
+ const aliases = this.parseJsonObject(
132
+ this.rc.webdriver_aliases,
133
+ 'webdriver_aliases',
134
+ );
135
+ if (!aliases) {
136
+ return;
137
+ }
138
+
139
+ for (const [name, alias] of Object.entries(aliases)) {
140
+ if (baseBrowsers.includes(name)) {
141
+ throw new Error(
142
+ `Settings: webdriver alias '${name}' conflicts with built-in browser`,
143
+ );
144
+ }
145
+ if (!alias || typeof alias != 'object') {
146
+ throw new Error(
147
+ `Settings: webdriver alias '${name}' must be an object`,
148
+ );
149
+ }
150
+ if (!alias.browser) {
151
+ throw new Error(
152
+ `Settings: webdriver alias '${name}' is missing required 'browser' field`,
153
+ );
154
+ }
155
+ if (!baseBrowsers.includes(alias.browser)) {
156
+ throw new Error(
157
+ `Settings: webdriver alias '${name}' has unexpected browser '${alias.browser}'`,
158
+ );
159
+ }
160
+ this.webdriver_aliases[name] = alias;
161
+ }
162
+ }
163
+
164
+ isKnownWebdriver(name) {
165
+ return baseBrowsers.includes(name) || name in this.webdriver_aliases;
166
+ }
167
+
168
+ resolveWebdriver(name) {
169
+ if (!name) {
170
+ throw new Error(`Settings: no webdriver name provided`);
171
+ }
172
+
173
+ const alias = this.webdriver_aliases[name];
174
+ if (alias) {
175
+ const flags = {
176
+ ...baseBrowserDefaults[alias.browser],
177
+ };
178
+ if (alias.isMobile) {
179
+ flags.isMobile = true;
180
+ }
181
+ if (alias.browserLoggingOn != null) {
182
+ flags.browserLoggingOn = alias.browserLoggingOn;
183
+ }
184
+
185
+ return {
186
+ name,
187
+ browser: alias.browser,
188
+ flags,
189
+ chrome: alias.chrome || null,
190
+ windowWidth: alias.windowWidth,
191
+ windowHeight: alias.windowHeight,
192
+ };
193
+ }
194
+
195
+ if (baseBrowsers.includes(name)) {
196
+ return {
197
+ name,
198
+ browser: name,
199
+ flags: { ...baseBrowserDefaults[name] },
200
+ chrome: null,
201
+ windowWidth: undefined,
202
+ windowHeight: undefined,
203
+ };
204
+ }
205
+
206
+ throw new Error(
207
+ `Settings: unexpected webdriver '${name}', define it in webdriver_aliases`,
208
+ );
209
+ }
210
+
211
+ validateWebdrivers() {
212
+ if (!this.webdrivers) {
213
+ return;
214
+ }
215
+ if (!(this.webdrivers instanceof Array)) {
216
+ throw new Error(
217
+ `Settings: webdrivers must be an array, got: ${JSON.stringify(this.webdrivers)}`,
218
+ );
219
+ }
220
+
221
+ for (const webdriver of this.webdrivers) {
222
+ if (!this.isKnownWebdriver(webdriver)) {
223
+ throw new Error(
224
+ `Settings: unexpected webdriver '${webdriver}', define it in webdriver_aliases`,
225
+ );
226
+ }
227
+ }
228
+ }
229
+
97
230
  setupWebdrivers() {
231
+ this.setupWebdriverAliases();
232
+
98
233
  this.webdrivers = this.rc.webdrivers;
99
234
  if (typeof this.rc.webdrivers == 'string') {
100
235
  try {
@@ -107,6 +242,8 @@ class Settings {
107
242
  }
108
243
  }
109
244
 
245
+ this.validateWebdrivers();
246
+
110
247
  this.webdriver = null;
111
248
  this.webdriver_headless =
112
249
  this.rc.webdriver_headless == true ||
@@ -142,6 +279,13 @@ class Settings {
142
279
  if (this.webdrivers && !this.silent) {
143
280
  console.log(`Settings: ${this.webdrivers.join(', ')} webdrivers`);
144
281
  }
282
+
283
+ const aliasCount = Object.keys(this.webdriver_aliases).length;
284
+ if (aliasCount > 0 && !this.silent) {
285
+ console.log(
286
+ `Settings: ${aliasCount} webdriver alias(es): ${Object.keys(this.webdriver_aliases).join(', ')}`,
287
+ );
288
+ }
145
289
  }
146
290
  }
147
291
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camperaid/watest",
3
- "version": "2.6.2",
3
+ "version": "2.6.3",
4
4
  "description": "Web Application Testsuite",
5
5
  "type": "module",
6
6
  "engines": {
@@ -0,0 +1,179 @@
1
+ import { is, throws } from '../base/test.js';
2
+ import { settings } from '../../core/settings.js';
3
+ import { DriverBase } from '../../webdriver/driver-base.js';
4
+
5
+ function withSettings(rcPatch, fn) {
6
+ const saved = {
7
+ rc: { ...settings.rc },
8
+ webdriver_aliases: { ...settings.webdriver_aliases },
9
+ webdrivers: settings.webdrivers,
10
+ };
11
+ settings.rc = { ...settings.rc, ...rcPatch };
12
+ settings.silent = true;
13
+ try {
14
+ settings.setupWebdrivers();
15
+ return fn();
16
+ } finally {
17
+ settings.rc = saved.rc;
18
+ settings.webdriver_aliases = saved.webdriver_aliases;
19
+ settings.webdrivers = saved.webdrivers;
20
+ settings.silent = false;
21
+ }
22
+ }
23
+
24
+ export function test() {
25
+ const chromeMobileAlias = {
26
+ browser: 'chrome',
27
+ isMobile: true,
28
+ chrome: {
29
+ mobileEmulation: { deviceName: 'iPhone 7' },
30
+ },
31
+ };
32
+
33
+ withSettings(
34
+ {
35
+ webdriver_aliases: {
36
+ 'chrome-mobile': chromeMobileAlias,
37
+ },
38
+ },
39
+ () => {
40
+ is(
41
+ settings.resolveWebdriver('chrome-mobile'),
42
+ {
43
+ name: 'chrome-mobile',
44
+ browser: 'chrome',
45
+ flags: {
46
+ chrome: true,
47
+ browserLoggingOn: true,
48
+ isMobile: true,
49
+ },
50
+ chrome: {
51
+ mobileEmulation: { deviceName: 'iPhone 7' },
52
+ },
53
+ windowWidth: undefined,
54
+ windowHeight: undefined,
55
+ },
56
+ 'resolve chrome-mobile alias',
57
+ );
58
+
59
+ is(
60
+ settings.resolveWebdriver('chrome'),
61
+ {
62
+ name: 'chrome',
63
+ browser: 'chrome',
64
+ flags: {
65
+ chrome: true,
66
+ browserLoggingOn: true,
67
+ },
68
+ chrome: null,
69
+ windowWidth: undefined,
70
+ windowHeight: undefined,
71
+ },
72
+ 'resolve chrome base browser',
73
+ );
74
+
75
+ is(settings.isKnownWebdriver('chrome-mobile'), true, 'known alias');
76
+ is(settings.isKnownWebdriver('firefox'), true, 'known base browser');
77
+ is(settings.isKnownWebdriver('unknown'), false, 'unknown webdriver');
78
+ },
79
+ );
80
+
81
+ withSettings(
82
+ {
83
+ webdriver_aliases: JSON.stringify({
84
+ 'firefox-mobile': {
85
+ browser: 'firefox',
86
+ isMobile: true,
87
+ },
88
+ }),
89
+ },
90
+ () => {
91
+ is(
92
+ settings.resolveWebdriver('firefox-mobile').browser,
93
+ 'firefox',
94
+ 'parse alias JSON string',
95
+ );
96
+ is(
97
+ DriverBase.isStdOutLogging('firefox-mobile'),
98
+ true,
99
+ 'firefox alias uses stdout logging',
100
+ );
101
+ },
102
+ );
103
+
104
+ withSettings(
105
+ {
106
+ webdriver_aliases: {
107
+ 'safari-mobile': {
108
+ browser: 'safari',
109
+ isMobile: true,
110
+ windowWidth: 375,
111
+ windowHeight: 667,
112
+ },
113
+ },
114
+ },
115
+ () => {
116
+ is(
117
+ settings.resolveWebdriver('safari-mobile'),
118
+ {
119
+ name: 'safari-mobile',
120
+ browser: 'safari',
121
+ flags: {
122
+ safari: true,
123
+ isMobile: true,
124
+ },
125
+ chrome: null,
126
+ windowWidth: 375,
127
+ windowHeight: 667,
128
+ },
129
+ 'resolve safari-mobile alias',
130
+ );
131
+ },
132
+ );
133
+
134
+ withSettings(
135
+ {
136
+ webdrivers: ['chrome-mobile'],
137
+ webdriver_aliases: {
138
+ 'chrome-mobile': chromeMobileAlias,
139
+ },
140
+ },
141
+ () => {
142
+ is(settings.webdrivers, ['chrome-mobile'], 'validate known alias');
143
+ },
144
+ );
145
+
146
+ throws(
147
+ () =>
148
+ withSettings(
149
+ {
150
+ webdrivers: ['chrome-mobile'],
151
+ },
152
+ () => {},
153
+ ),
154
+ /unexpected webdriver 'chrome-mobile'/,
155
+ 'reject undefined alias in webdrivers',
156
+ );
157
+
158
+ throws(
159
+ () =>
160
+ withSettings(
161
+ {
162
+ webdriver_aliases: {
163
+ chrome: {
164
+ browser: 'chrome',
165
+ },
166
+ },
167
+ },
168
+ () => {},
169
+ ),
170
+ /conflicts with built-in browser/,
171
+ 'reject alias colliding with base browser',
172
+ );
173
+
174
+ throws(
175
+ () => settings.resolveWebdriver('unknown-alias'),
176
+ /unexpected webdriver 'unknown-alias'/,
177
+ 'reject unknown webdriver resolution',
178
+ );
179
+ }
@@ -16,7 +16,7 @@
16
16
  },
17
17
  "../../../..": {
18
18
  "name": "@camperaid/watest",
19
- "version": "2.6.2",
19
+ "version": "2.6.3",
20
20
  "dev": true,
21
21
  "license": "MPL",
22
22
  "dependencies": {
@@ -16,7 +16,7 @@
16
16
  },
17
17
  "../../../..": {
18
18
  "name": "@camperaid/watest",
19
- "version": "2.6.2",
19
+ "version": "2.6.3",
20
20
  "dev": true,
21
21
  "license": "MPL",
22
22
  "dependencies": {
@@ -16,7 +16,7 @@
16
16
  },
17
17
  "../../../..": {
18
18
  "name": "@camperaid/watest",
19
- "version": "2.6.2",
19
+ "version": "2.6.3",
20
20
  "dev": true,
21
21
  "license": "MPL",
22
22
  "dependencies": {
@@ -16,7 +16,7 @@
16
16
  },
17
17
  "../../../..": {
18
18
  "name": "@camperaid/watest",
19
- "version": "2.6.2",
19
+ "version": "2.6.3",
20
20
  "dev": true,
21
21
  "license": "MPL",
22
22
  "dependencies": {
@@ -16,7 +16,7 @@
16
16
  },
17
17
  "../../../..": {
18
18
  "name": "@camperaid/watest",
19
- "version": "2.6.2",
19
+ "version": "2.6.3",
20
20
  "dev": true,
21
21
  "license": "MPL",
22
22
  "dependencies": {
package/tests/meta.js CHANGED
@@ -1 +1 @@
1
- export var folders = ['base', 'series', 'webdriver', 'e2e', 'deps'];
1
+ export var folders = ['base', 'core', 'series', 'webdriver', 'e2e', 'deps'];
@@ -24,18 +24,21 @@ import {
24
24
  import firefox from 'selenium-webdriver/firefox.js';
25
25
  import chrome from 'selenium-webdriver/chrome.js';
26
26
 
27
- function getChromeOptions() {
27
+ function getChromeOptions(resolved) {
28
+ const windowWidth =
29
+ resolved.windowWidth ?? settings.webdriver_window_width;
30
+ const windowHeight =
31
+ resolved.windowHeight ?? settings.webdriver_window_height;
32
+
28
33
  const chromeOptions = new chrome.Options().windowSize({
29
- width: settings.webdriver_window_width,
30
- height: settings.webdriver_window_height,
34
+ width: windowWidth,
35
+ height: windowHeight,
31
36
  });
32
37
  if (settings.webdriver_headless) {
33
38
  chromeOptions.addArguments('headless=new');
34
39
  }
35
- if (settings.webdriver == 'chrome-mobile') {
36
- chromeOptions.setMobileEmulation({
37
- deviceName: 'iPhone 7',
38
- });
40
+ if (resolved.chrome?.mobileEmulation) {
41
+ chromeOptions.setMobileEmulation(resolved.chrome.mobileEmulation);
39
42
  }
40
43
  // Accept self-signed certificates (for k3s testing)
41
44
  chromeOptions.addArguments('ignore-certificate-errors');
@@ -63,6 +66,15 @@ function getFirefoxArgs() {
63
66
  return settings.webdriver_headless ? ['-headless'] : [];
64
67
  }
65
68
 
69
+ async function applyResolvedWindowSize(driver, resolved) {
70
+ if (resolved.windowWidth == null && resolved.windowHeight == null) {
71
+ return;
72
+ }
73
+ const width = resolved.windowWidth ?? settings.webdriver_window_width;
74
+ const height = resolved.windowHeight ?? settings.webdriver_window_height;
75
+ await driver.manage().window().setRect({ width, height, x: 0, y: 0 });
76
+ }
77
+
66
78
  const defaultTimeout = 10;
67
79
  const getTimeout = () => (testflow.core.getTimeout() || defaultTimeout) * 1000;
68
80
  const getTimeoutOnFailure = () => (testflow.core.getTimeout() || 0) * 1000; // use non zero values for failure debugging
@@ -107,9 +119,10 @@ class DriverBase {
107
119
  * Creates an instance of web driver.
108
120
  */
109
121
  static async build() {
110
- log(`Build WebDriver for '${settings.webdriver}'`);
122
+ const resolved = settings.resolveWebdriver(settings.webdriver);
123
+ log(`Build WebDriver for '${resolved.name}'`);
111
124
 
112
- switch (settings.webdriver) {
125
+ switch (resolved.browser) {
113
126
  case 'chrome': {
114
127
  const driver = await new Builder()
115
128
  .withCapabilities({
@@ -118,14 +131,13 @@ class DriverBase {
118
131
  browser: browserLogLevel,
119
132
  },
120
133
  })
121
- .setChromeOptions(getChromeOptions())
134
+ .setChromeOptions(getChromeOptions(resolved))
122
135
  .build();
123
136
 
124
137
  return [
125
138
  driver,
126
139
  {
127
- browserLoggingOn: true,
128
- chrome: true,
140
+ ...resolved.flags,
129
141
  },
130
142
  ];
131
143
  }
@@ -154,38 +166,18 @@ class DriverBase {
154
166
  return [
155
167
  driver,
156
168
  {
157
- firefox: true,
158
- },
159
- ];
160
- }
161
-
162
- case 'chrome-mobile': {
163
- const driver = await new Builder()
164
- .withCapabilities({
165
- 'browserName': Browser.CHROME,
166
- 'goog:loggingPrefs': {
167
- browser: browserLogLevel,
168
- },
169
- })
170
- .setChromeOptions(getChromeOptions())
171
- .build();
172
-
173
- return [
174
- driver,
175
- {
176
- isMobile: true,
177
- chrome: true,
178
- browserLoggingOn: true,
169
+ ...resolved.flags,
179
170
  },
180
171
  ];
181
172
  }
182
173
 
183
174
  case 'safari': {
184
175
  const driver = await new Builder().forBrowser(Browser.SAFARI).build();
176
+ await applyResolvedWindowSize(driver, resolved);
185
177
  return [
186
178
  driver,
187
179
  {
188
- safari: true,
180
+ ...resolved.flags,
189
181
  },
190
182
  ];
191
183
  }
@@ -195,18 +187,18 @@ class DriverBase {
195
187
  return [
196
188
  driver,
197
189
  {
198
- edge: true,
190
+ ...resolved.flags,
199
191
  },
200
192
  ];
201
193
  }
202
194
 
203
195
  default:
204
- throw new Error(`Unexpected '${settings.webdriver}' webdriver`);
196
+ throw new Error(`Unexpected '${resolved.browser}' webdriver browser`);
205
197
  }
206
198
  }
207
199
 
208
200
  static isStdOutLogging(webdriver) {
209
- return webdriver == 'firefox';
201
+ return settings.resolveWebdriver(webdriver).browser == 'firefox';
210
202
  }
211
203
 
212
204
  static get Errors() {