selenium-side-runner 3.17.0 → 4.0.0-alpha.16

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.
Files changed (60) hide show
  1. package/README.md +29 -10
  2. package/dist/bin.d.ts +3 -0
  3. package/dist/bin.d.ts.map +1 -0
  4. package/dist/bin.js +160 -0
  5. package/dist/bin.js.map +1 -0
  6. package/dist/capabilities.d.ts +7 -0
  7. package/dist/capabilities.d.ts.map +1 -0
  8. package/dist/capabilities.js +49 -66
  9. package/dist/capabilities.js.map +1 -0
  10. package/dist/config.d.ts +6 -0
  11. package/dist/config.d.ts.map +1 -0
  12. package/dist/config.js +12 -18
  13. package/dist/config.js.map +1 -0
  14. package/dist/main.test.d.ts +2 -0
  15. package/dist/main.test.d.ts.map +1 -0
  16. package/dist/main.test.js +113 -0
  17. package/dist/main.test.js.map +1 -0
  18. package/dist/proxy.d.ts +3 -0
  19. package/dist/proxy.d.ts.map +1 -0
  20. package/dist/proxy.js +67 -64
  21. package/dist/proxy.js.map +1 -0
  22. package/dist/register.d.ts +16 -0
  23. package/dist/register.d.ts.map +1 -0
  24. package/dist/register.js +66 -0
  25. package/dist/register.js.map +1 -0
  26. package/dist/run.d.ts +14 -0
  27. package/dist/run.d.ts.map +1 -0
  28. package/dist/run.js +169 -0
  29. package/dist/run.js.map +1 -0
  30. package/dist/types.d.ts +44 -0
  31. package/dist/types.d.ts.map +1 -0
  32. package/dist/types.js +3 -0
  33. package/dist/types.js.map +1 -0
  34. package/dist/versioner.d.ts +2 -0
  35. package/dist/versioner.d.ts.map +1 -0
  36. package/dist/versioner.js +23 -22
  37. package/dist/versioner.js.map +1 -0
  38. package/jest.config.js +13 -0
  39. package/package.json +30 -25
  40. package/src/__tests__/capabilities.spec.ts +137 -0
  41. package/{dist/npm.js → src/__tests__/config.spec.ts} +8 -24
  42. package/src/__tests__/config_1.yml +4 -0
  43. package/src/__tests__/config_2.yml +3 -0
  44. package/src/__tests__/proxy.spec.ts +138 -0
  45. package/src/__tests__/versioner.spec.ts +44 -0
  46. package/src/bin.ts +202 -0
  47. package/src/capabilities.ts +87 -0
  48. package/{dist/util.js → src/config.ts} +11 -10
  49. package/src/main.test.ts +129 -0
  50. package/src/proxy.ts +89 -0
  51. package/src/register.ts +79 -0
  52. package/src/run.ts +185 -0
  53. package/src/types.ts +82 -0
  54. package/src/versioner.ts +36 -0
  55. package/tsconfig.json +19 -0
  56. package/tsconfig.tsbuildinfo +1 -0
  57. package/.babelrc +0 -12
  58. package/dist/child.js +0 -7
  59. package/dist/index.js +0 -391
  60. package/dist/module-path.js +0 -41
@@ -0,0 +1,87 @@
1
+ // Licensed to the Software Freedom Conservancy (SFC) under one
2
+ // or more contributor license agreements. See the NOTICE file
3
+ // distributed with this work for additional information
4
+ // regarding copyright ownership. The SFC licenses this file
5
+ // to you under the Apache License, Version 2.0 (the
6
+ // "License"); you may not use this file except in compliance
7
+ // with the License. You may obtain a copy of the License at
8
+ //
9
+ // http://www.apache.org/licenses/LICENSE-2.0
10
+ //
11
+ // Unless required by applicable law or agreed to in writing,
12
+ // software distributed under the License is distributed on an
13
+ // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ // KIND, either express or implied. See the License for the
15
+ // specific language governing permissions and limitations
16
+ // under the License.
17
+
18
+ import { JSON } from './types'
19
+
20
+ export function parseString(input: string): Record<string, JSON> {
21
+ const capabilities = {}
22
+
23
+ matchStringPairs(input).forEach(({ key, value }) => {
24
+ assignStringKey(key, parseStringValue(value), capabilities)
25
+ })
26
+
27
+ return capabilities
28
+ }
29
+
30
+ export default {
31
+ parseString,
32
+ }
33
+
34
+ function matchStringPairs(input: string) {
35
+ const regex = /([^\s=]*)\s?=\s?(".*?"|'.*?'|\[.*\]|[^\s]*)/g
36
+ let result
37
+ const splitCapabilities = []
38
+ while ((result = regex.exec(input)) !== null) {
39
+ splitCapabilities.push({ key: result[1], value: result[2] })
40
+ }
41
+
42
+ return splitCapabilities
43
+ }
44
+
45
+ type KeyObject = Record<string, string | Record<string, string>>
46
+
47
+ function assignStringKey(key: string, value: string, keyObject: KeyObject) {
48
+ keyObject = keyObject || {}
49
+
50
+ // @ts-expect-error
51
+ key.split('.').reduce((objectKey, currKey, index, keys) => {
52
+ const ref =
53
+ !objectKey[currKey] || typeof objectKey[currKey] !== 'object'
54
+ ? {}
55
+ : objectKey[currKey]
56
+ objectKey[currKey] = keys.length === index + 1 ? value : ref
57
+ return ref
58
+ }, keyObject)
59
+
60
+ return keyObject
61
+ }
62
+
63
+ function parseStringValue(value: string) {
64
+ // is array
65
+ if (/^\[.*\]$/.test(value)) {
66
+ const matches = value.slice(1, -2).match(/(([^,]*)*)/g) as string[]
67
+ return matches.filter((s) => !!s).map((s) => s.trim())
68
+ }
69
+ try {
70
+ let parsed = JSON.parse(value)
71
+ if (typeof parsed === 'string') {
72
+ parsed = parsed.trim()
73
+ }
74
+ return parsed
75
+ } catch (e) {
76
+ // single quote is illegal in JSON
77
+ return trimSingleQuote(value)
78
+ }
79
+ }
80
+
81
+ function trimSingleQuote(value: string) {
82
+ let str = value.trim()
83
+ if (str[0] === str[str.length - 1] && str[0] === "'") {
84
+ return str.slice(1, -2)
85
+ }
86
+ return str
87
+ }
@@ -1,10 +1,3 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.sanitizeFileName = sanitizeFileName;
7
-
8
1
  // Licensed to the Software Freedom Conservancy (SFC) under one
9
2
  // or more contributor license agreements. See the NOTICE file
10
3
  // distributed with this work for additional information
@@ -21,6 +14,14 @@ exports.sanitizeFileName = sanitizeFileName;
21
14
  // KIND, either express or implied. See the License for the
22
15
  // specific language governing permissions and limitations
23
16
  // under the License.
24
- function sanitizeFileName(name) {
25
- return name.replace(/^([^a-z0-9])|([^a-z0-9._-]*)/gi, '');
26
- }
17
+
18
+ import fs from 'fs'
19
+ import yaml from 'js-yaml'
20
+
21
+ export function load(config: string) {
22
+ return yaml.load(fs.readFileSync(config, 'utf8'))
23
+ }
24
+
25
+ export default {
26
+ load,
27
+ }
@@ -0,0 +1,129 @@
1
+ // Licensed to the Software Freedom Conservancy (SFC) under one
2
+ // or more contributor license agreements. See the NOTICE file
3
+ // distributed with this work for additional information
4
+ // regarding copyright ownership. The SFC licenses this file
5
+ // to you under the Apache License, Version 2.0 (the
6
+ // "License"); you may not use this file except in compliance
7
+ // with the License. You may obtain a copy of the License at
8
+ //
9
+ // http://www.apache.org/licenses/LICENSE-2.0
10
+ //
11
+ // Unless required by applicable law or agreed to in writing,
12
+ // software distributed under the License is distributed on an
13
+ // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ // KIND, either express or implied. See the License for the
15
+ // specific language governing permissions and limitations
16
+ // under the License.
17
+
18
+ import each from 'jest-each'
19
+ import fs from 'fs'
20
+ import glob from 'glob'
21
+ import { createLogger, format, transports } from 'winston'
22
+ import { Configuration, Project } from './types'
23
+ import buildRegister from './register'
24
+ import buildRunners from './run'
25
+ import { SuiteShape, TestShape } from '@seleniumhq/side-model'
26
+
27
+ const metadata = require('../package.json')
28
+
29
+ process.title = metadata.name
30
+
31
+ // Capture and show unhandled exceptions
32
+ process.on('unhandledRejection', function handleWarning(reason) {
33
+ console.log('[PROCESS] Unhandled Promise Rejection')
34
+ console.log('- - - - - - - - - - - - - - - - - - -')
35
+ console.log(reason)
36
+ console.log('- -')
37
+ })
38
+
39
+ process.on('uncaughtException', (error) => {
40
+ console.error('Unhandled Error', error)
41
+ })
42
+
43
+ const configuration: Configuration = JSON.parse(
44
+ process.env.SE_CONFIGURATION as string
45
+ )
46
+
47
+ // Jest timeout should be really far back, otherwise it will impede people
48
+ // When working right, we should close shop and detonate on our own much sooner
49
+ jest.setTimeout(60 * 60 * 1000) // 60 minutes before we just cut it off
50
+
51
+ const logger = createLogger({
52
+ level: configuration.debug ? 'debug' : 'info',
53
+ transports: [
54
+ new transports.Console({
55
+ format: format.combine(format.colorize(), format.simple()),
56
+ }),
57
+ ],
58
+ })
59
+
60
+ const projectTitle = 'Running project $name'
61
+ const suiteTitle = 'Running suite $name'
62
+ const testTitle = 'Running test $name'
63
+
64
+ const projects: Project[] = [
65
+ ...configuration.projects.reduce((projects, project) => {
66
+ glob.sync(project).forEach((p) => {
67
+ projects.add(p)
68
+ })
69
+ return projects
70
+ }, new Set<string>()),
71
+ ]
72
+ .map((p) => {
73
+ const project = JSON.parse(fs.readFileSync(p, 'utf8'))
74
+ project.path = p
75
+ return project
76
+ })
77
+ .filter((p) => Boolean(p))
78
+
79
+ if (!projects.length) {
80
+ throw new Error(
81
+ 'No matching projects found, supplied filepaths: ' +
82
+ configuration.projects.join(', ')
83
+ )
84
+ }
85
+
86
+ const factoryParams = {
87
+ configuration,
88
+ logger: logger as unknown as Console,
89
+ }
90
+ const register = buildRegister(factoryParams)
91
+ const runners = buildRunners(factoryParams)
92
+ each(projects).describe(projectTitle, (project: Project) => {
93
+ const suites = project.suites.filter(
94
+ (suite) =>
95
+ suite.tests.length && new RegExp(configuration.filter).test(suite.name)
96
+ )
97
+ if (suites.length) {
98
+ each(suites).describe(suiteTitle, (suite: SuiteShape) => {
99
+ const isParallel = suite.parallel
100
+ const tests = suite.tests
101
+ .map((tID) => register.test(project, tID))
102
+ .filter((test) => new RegExp(configuration.filter).test(test.name))
103
+ const testExecutor = each(tests)
104
+ const testMethod = isParallel
105
+ ? testExecutor.test.concurrent
106
+ : testExecutor.test
107
+ testMethod(testTitle, async (test: TestShape) => {
108
+ await runners.test(project, test)
109
+ })
110
+ })
111
+ } else {
112
+ console.info(`
113
+ Project ${project.name} doesn't have any suites matching filter ${configuration.filter},
114
+ attempting to iterate all tests in project
115
+ `)
116
+ const tests = project.tests
117
+ .map((test) => register.test(project, test.id))
118
+ .filter((test) => new RegExp(configuration.filter).test(test.name))
119
+ if (!tests.length) {
120
+ throw new Error(
121
+ `No suites or tests found in project ${project.name} matching filter ${configuration.filter}, unable to test!`
122
+ )
123
+ }
124
+ const testExecutor = each(tests)
125
+ testExecutor.test(testTitle, async (test: TestShape) => {
126
+ await runners.test(project, test)
127
+ })
128
+ }
129
+ })
package/src/proxy.ts ADDED
@@ -0,0 +1,89 @@
1
+ // Licensed to the Software Freedom Conservancy (SFC) under one
2
+ // or more contributor license agreements. See the NOTICE file
3
+ // distributed with this work for additional information
4
+ // regarding copyright ownership. The SFC licenses this file
5
+ // to you under the Apache License, Version 2.0 (the
6
+ // "License"); you may not use this file except in compliance
7
+ // with the License. You may obtain a copy of the License at
8
+ //
9
+ // http://www.apache.org/licenses/LICENSE-2.0
10
+ //
11
+ // Unless required by applicable law or agreed to in writing,
12
+ // software distributed under the License is distributed on an
13
+ // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ // KIND, either express or implied. See the License for the
15
+ // specific language governing permissions and limitations
16
+ // under the License.
17
+
18
+ import { ProxyCapabilities, ProxyInputOptions, ProxyType } from './types'
19
+
20
+ export default function ParseProxy(
21
+ type: ProxyType,
22
+ options?: ProxyInputOptions
23
+ ): ProxyCapabilities {
24
+ if (type === 'direct' || type === 'system') {
25
+ return {
26
+ proxyType: type,
27
+ }
28
+ } else if (type === 'pac') {
29
+ if (!options || typeof options !== 'string') {
30
+ throw new Error(
31
+ 'A proxy autoconfig URL was not passed (e.g. --proxy-options="http://localhost/pac")'
32
+ )
33
+ }
34
+ return {
35
+ proxyType: type,
36
+ proxyAutoconfigUrl: options,
37
+ }
38
+ } else if (type === 'manual') {
39
+ if (!options) {
40
+ return {
41
+ proxyType: type,
42
+ }
43
+ } else if (typeof options !== 'object') {
44
+ throw new Error(
45
+ 'Proxy options were not passed to manual proxy (e.g. --proxy-options="http=localhost:321 ftp=localhost:4324")'
46
+ )
47
+ } else {
48
+ let opts: Omit<ProxyCapabilities, 'proxyType'> = {}
49
+ if (options.http) opts.httpProxy = options.http as string
50
+ if (options.https) opts.sslProxy = options.https as string
51
+ if (options.ftp) opts.ftpProxy = options.ftp as string
52
+ if (options.bypass) opts.noProxy = options.bypass as string[]
53
+ return {
54
+ proxyType: type,
55
+ ...opts,
56
+ }
57
+ }
58
+ } else if (type === 'socks') {
59
+ if (!options || typeof options === 'string' || !options.socksProxy) {
60
+ throw new Error(
61
+ 'Proxy options were not passed to socks proxy (e.g. --proxy-options="socksProxy=localhost:321")'
62
+ )
63
+ } else {
64
+ if (options.socksVersion) {
65
+ const ver = parseInt(options.socksVersion as string)
66
+ if (ver) {
67
+ return {
68
+ proxyType: 'manual',
69
+ socksProxy: options.socksProxy as string,
70
+ socksVersion: ver,
71
+ }
72
+ } else {
73
+ throw new Error(
74
+ 'Proxy socks version is invalid (e.g. --proxy-options="socksProxy=localhost:321 socksVersion=5")'
75
+ )
76
+ }
77
+ } else {
78
+ return {
79
+ proxyType: 'manual',
80
+ socksProxy: options.socksProxy as string,
81
+ }
82
+ }
83
+ }
84
+ } else {
85
+ throw new Error(
86
+ 'An unknown proxy type was passed, use one of: direct, system, manual, socks or pac (e.g. --proxy-type="direct")'
87
+ )
88
+ }
89
+ }
@@ -0,0 +1,79 @@
1
+ // Licensed to the Software Freedom Conservancy (SFC) under one
2
+ // or more contributor license agreements. See the NOTICE file
3
+ // distributed with this work for additional information
4
+ // regarding copyright ownership. The SFC licenses this file
5
+ // to you under the Apache License, Version 2.0 (the
6
+ // "License"); you may not use this file except in compliance
7
+ // with the License. You may obtain a copy of the License at
8
+ //
9
+ // http://www.apache.org/licenses/LICENSE-2.0
10
+ //
11
+ // Unless required by applicable law or agreed to in writing,
12
+ // software distributed under the License is distributed on an
13
+ // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ // KIND, either express or implied. See the License for the
15
+ // specific language governing permissions and limitations
16
+ // under the License.
17
+
18
+ import { ProjectShape, SuiteShape, TestShape } from '@seleniumhq/side-model'
19
+ import Satisfies from './versioner'
20
+ import { Configuration, Project } from './types'
21
+
22
+ export interface HoistedThings {
23
+ configuration: Configuration
24
+ logger: Console
25
+ }
26
+
27
+ export type SuiteArgs = [SuiteShape, TestShape[]]
28
+ export type ProjectArgs = [ProjectShape, SuiteArgs[]]
29
+ const buildRunners = ({ configuration, logger }: HoistedThings) => {
30
+ const registerTest = (project: Project, testID: string) =>
31
+ project.tests.find((t) => t.id === testID) as TestShape
32
+
33
+ const registerSuite = (project: Project, suite: SuiteShape): SuiteArgs => {
34
+ let tests: TestShape[] = []
35
+ for (let i = 0, ii = suite.tests.length; i != ii; i++) {
36
+ tests.push(registerTest(project, suite.tests[i]))
37
+ }
38
+ return [suite, tests]
39
+ }
40
+
41
+ const registerProject = (project: Project): ProjectArgs => {
42
+ if (!configuration.force) {
43
+ let warning = Satisfies(project.version, '2.0')
44
+ if (warning) {
45
+ logger.warn(warning)
46
+ }
47
+ } else {
48
+ logger.warn("--force is set, ignoring project's version")
49
+ }
50
+ if (!project.suites.length) {
51
+ throw new Error(
52
+ `The project ${project.name} has no test suites defined, create a suite using the IDE.`
53
+ )
54
+ }
55
+
56
+ const suites: SuiteArgs[] = []
57
+ for (let i = 0, ii = project.suites.length; i != ii; i++) {
58
+ suites.push(registerSuite(project, project.suites[i]))
59
+ }
60
+ return [project, suites]
61
+ }
62
+
63
+ const registerAll = (projectConfigs: Project[]): ProjectArgs[] => {
64
+ const projects: ProjectArgs[] = []
65
+ for (let i = 0, ii = projectConfigs.length; i != ii; i++) {
66
+ projects.push(registerProject(projectConfigs[i]))
67
+ }
68
+ return projects
69
+ }
70
+
71
+ return {
72
+ all: registerAll,
73
+ project: registerProject,
74
+ suite: registerSuite,
75
+ test: registerTest,
76
+ }
77
+ }
78
+
79
+ export default buildRunners
package/src/run.ts ADDED
@@ -0,0 +1,185 @@
1
+ // Licensed to the Software Freedom Conservancy (SFC) under one
2
+ // or more contributor license agreements. See the NOTICE file
3
+ // distributed with this work for additional information
4
+ // regarding copyright ownership. The SFC licenses this file
5
+ // to you under the Apache License, Version 2.0 (the
6
+ // "License"); you may not use this file except in compliance
7
+ // with the License. You may obtain a copy of the License at
8
+ //
9
+ // http://www.apache.org/licenses/LICENSE-2.0
10
+ //
11
+ // Unless required by applicable law or agreed to in writing,
12
+ // software distributed under the License is distributed on an
13
+ // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ // KIND, either express or implied. See the License for the
15
+ // specific language governing permissions and limitations
16
+ // under the License.
17
+
18
+ import {
19
+ getCustomCommands,
20
+ loadPlugins,
21
+ Playback,
22
+ PlaybackEvents,
23
+ PlaybackEventShapes,
24
+ Variables,
25
+ WebDriverExecutor,
26
+ } from '@seleniumhq/side-runtime'
27
+ import { WebDriverExecutorConstructorArgs } from '@seleniumhq/side-runtime/dist/webdriver'
28
+ import { SuiteShape, TestShape } from '@seleniumhq/side-model'
29
+ import * as path from 'path'
30
+ import Satisfies from './versioner'
31
+ import { Configuration, Project } from './types'
32
+
33
+ export interface HoistedThings {
34
+ configuration: Configuration
35
+ logger: Console
36
+ }
37
+
38
+ const buildRunners = ({ configuration, logger }: HoistedThings) => {
39
+ const runTest = async (project: Project, test: TestShape) => {
40
+ logger.info(`Running test ${test.name}`)
41
+ const shortenedProjectPath = project.path
42
+ .split(path.sep)
43
+ .slice(0, -1)
44
+ .join(path.sep)
45
+ const plugins = await loadPlugins(shortenedProjectPath, project.plugins)
46
+ const customCommands = getCustomCommands(plugins)
47
+ const driver = new WebDriverExecutor({
48
+ capabilities:
49
+ configuration.capabilities as unknown as WebDriverExecutorConstructorArgs['capabilities'],
50
+ customCommands,
51
+ hooks: {
52
+ onBeforePlay: async () => {
53
+ await Promise.all(
54
+ plugins.map((plugin) => {
55
+ const onBeforePlay = plugin.hooks.onBeforePlay
56
+ if (onBeforePlay) {
57
+ return onBeforePlay({ driver })
58
+ }
59
+ })
60
+ )
61
+ },
62
+ },
63
+ server: configuration.server,
64
+ })
65
+ await playbackUntilComplete()
66
+ function playbackUntilComplete() {
67
+ // eslint-disable-next-line no-async-promise-executor
68
+ return new Promise(async (resolve, reject) => {
69
+ const playback = new Playback({
70
+ baseUrl: project.url,
71
+ executor: driver,
72
+ getTestByName: (name: string) =>
73
+ project.tests.find((t) => t.name === name) as TestShape,
74
+ logger,
75
+ variables: new Variables(),
76
+ })
77
+ const onComplete = (failure: any) => {
78
+ playback.cleanup()
79
+ if (failure) {
80
+ logger.warn('Completed with failure', failure)
81
+ return reject(failure)
82
+ } else {
83
+ return resolve(null)
84
+ }
85
+ }
86
+ const EE = playback['event-emitter']
87
+ EE.addListener(
88
+ PlaybackEvents.PLAYBACK_STATE_CHANGED,
89
+ ({ state }: PlaybackEventShapes['PLAYBACK_STATE_CHANGED']) => {
90
+ logger.debug(`Playing state changed ${state}`)
91
+ switch (state) {
92
+ case 'aborted':
93
+ case 'errored':
94
+ case 'failed':
95
+ case 'finished':
96
+ case 'paused':
97
+ case 'stopped':
98
+ logger.info(
99
+ `Finished test ${test.name} ${
100
+ state === 'finished' ? 'Success' : 'Failure'
101
+ }`
102
+ )
103
+ onComplete(state !== 'finished')
104
+ }
105
+ }
106
+ )
107
+ EE.addListener(
108
+ PlaybackEvents.COMMAND_STATE_CHANGED,
109
+ ({
110
+ command,
111
+ state,
112
+ }: PlaybackEventShapes['COMMAND_STATE_CHANGED']) => {
113
+ const cmd = command
114
+ const niceString = [cmd.command, cmd.target, cmd.value]
115
+ .filter((v) => !!v)
116
+ .join('|')
117
+ logger.debug(`${state} ${niceString}`)
118
+ }
119
+ )
120
+ try {
121
+ await playback.play(test, {
122
+ startingCommandIndex: 0,
123
+ })
124
+ } catch (e) {
125
+ console.error(e)
126
+ playback.cleanup()
127
+ return reject(e)
128
+ }
129
+ })
130
+ }
131
+ }
132
+
133
+ const runSuite = async (project: Project, suite: SuiteShape) => {
134
+ logger.info(`Running suite ${suite.name}`)
135
+ if (!suite.tests.length) {
136
+ throw new Error(
137
+ `The suite ${suite.name} has no tests, add tests to the suite using the IDE.`
138
+ )
139
+ }
140
+ for (let i = 0, ii = suite.tests.length; i != ii; i++) {
141
+ await runTest(
142
+ project,
143
+ project.tests.find((t) => t.id === suite.tests[i]) as TestShape
144
+ )
145
+ }
146
+ logger.info(`Finished suite ${suite.name}`)
147
+ }
148
+
149
+ const runProject = async (project: Project) => {
150
+ logger.info(`Running project ${project.name}`)
151
+ if (!configuration.force) {
152
+ let warning = Satisfies(project.version, '2.0')
153
+ if (warning) {
154
+ logger.warn(warning)
155
+ }
156
+ } else {
157
+ logger.warn("--force is set, ignoring project's version")
158
+ }
159
+ if (!project.suites.length) {
160
+ throw new Error(
161
+ `The project ${project.name} has no test suites defined, create a suite using the IDE.`
162
+ )
163
+ }
164
+
165
+ for (let i = 0, ii = project.suites.length; i != ii; i++) {
166
+ await runSuite(project, project.suites[i])
167
+ }
168
+ logger.info(`Finished project ${project.name}`)
169
+ }
170
+
171
+ const runAll = async (projects: Project[]) => {
172
+ for (let i = 0, ii = projects.length; i != ii; i++) {
173
+ await runProject(projects[i])
174
+ }
175
+ }
176
+
177
+ return {
178
+ all: runAll,
179
+ project: runProject,
180
+ suite: runSuite,
181
+ test: runTest,
182
+ }
183
+ }
184
+
185
+ export default buildRunners
package/src/types.ts ADDED
@@ -0,0 +1,82 @@
1
+ import { ProjectShape } from '@seleniumhq/side-model'
2
+ import { Command } from 'commander'
3
+
4
+ export type JSON =
5
+ | null
6
+ | string
7
+ | number
8
+ | boolean
9
+ | JSON[]
10
+ | { [key: string]: JSON }
11
+
12
+ export interface Project extends ProjectShape {
13
+ path: string
14
+ }
15
+
16
+ export type ProxyType =
17
+ | 'autodetect'
18
+ | 'direct'
19
+ | 'manual'
20
+ | 'pac'
21
+ | 'socks'
22
+ | 'system'
23
+
24
+ export type ProxyInputOptions =
25
+ | string
26
+ | Record<string, string | string[] | number>
27
+
28
+ export type ProxyCapabilities = {
29
+ proxyType: 'autodetect' | 'direct' | 'manual' | 'pac' | 'system'
30
+ proxyAutoconfigUrl?: string
31
+ ftpProxy?: string
32
+ httpProxy?: string
33
+ noProxy?: string[]
34
+ sslProxy?: string
35
+ socksProxy?: string
36
+ socksVersion?: number
37
+ }
38
+
39
+ export interface SideRunnerCLIConfig {
40
+ // Override the base URL that was set in the IDE
41
+ baseUrl: string
42
+ // Webdriver capabilities
43
+ capabilities: string
44
+ // Use specified YAML file for configuration. (default: .side.yml)
45
+ configFile: string
46
+ // Print debug logs
47
+ debug: boolean
48
+ // Run suites matching name
49
+ filter: string
50
+ // Forcibly run the project, regardless of project's version
51
+ force: boolean
52
+ // Maximum amount of workers that will run your tests, defaults to number of cores
53
+ maxWorkers: number
54
+ // General parameters
55
+ params: string
56
+ // Proxy options to pass, for use with manual, pac and socks proxies
57
+ proxyOptions?: string
58
+ // Type of proxy to use (one of: direct, manual, pac, socks, system)
59
+ proxyType?: ProxyType
60
+ // Webdriver remote server
61
+ server: string
62
+ // The maximimum amount of time, in milliseconds, to spend attempting to locate
63
+ // an element. (default: ${DEFAULT_TIMEOUT})
64
+ timeout: number
65
+ }
66
+
67
+ export type SideRunnerAPI = Command & SideRunnerCLIConfig
68
+
69
+ export type Configuration = Required<
70
+ Pick<
71
+ SideRunnerCLIConfig,
72
+ 'baseUrl' | 'filter' | 'maxWorkers' | 'server' | 'timeout'
73
+ >
74
+ > &
75
+ Pick<SideRunnerCLIConfig, 'debug' | 'force' | 'proxyType'> & {
76
+ capabilities: Record<string, JSON>
77
+ params: Record<string, JSON>
78
+ projects: string[]
79
+ proxyOptions: ProxyInputOptions
80
+ runId: string
81
+ path: string
82
+ }