selenium-side-runner 3.16.0 → 4.0.0-alpha.15

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 +31 -12
  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 +105 -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 +120 -0
  50. package/src/proxy.ts +89 -0
  51. package/src/register.ts +79 -0
  52. package/src/run.ts +182 -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,120 @@
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 util from 'util'
21
+ import glob from 'glob'
22
+ import { createLogger, format, transports } from 'winston'
23
+ import { Configuration, Project } from './types'
24
+ import buildRegister from './register'
25
+ import buildRunners from './run'
26
+ import { SuiteShape, TestShape } from '@seleniumhq/side-model'
27
+
28
+ const metadata = require('../package.json')
29
+
30
+ process.title = metadata.name
31
+
32
+ const configuration: Configuration = JSON.parse(
33
+ process.env.SE_CONFIGURATION as string
34
+ )
35
+
36
+ // Jest timeout should be really far back, otherwise it will impede people
37
+ // When working right, we should close shop and detonate on our own much sooner
38
+ jest.setTimeout(60 * 60 * 1000) // 60 minutes before we just cut it off
39
+
40
+ const logger = createLogger({
41
+ level: configuration.debug ? 'debug' : 'info',
42
+ transports: [
43
+ new transports.Console({
44
+ format: format.combine(format.colorize(), format.simple()),
45
+ }),
46
+ ],
47
+ })
48
+
49
+ logger.debug(util.inspect(configuration))
50
+
51
+ const projectTitle = 'Running project $name'
52
+ const suiteTitle = 'Running suite $name'
53
+ const testTitle = 'Running test $name'
54
+
55
+ const projects: Project[] = [
56
+ ...configuration.projects.reduce((projects, project) => {
57
+ glob.sync(project).forEach((p) => {
58
+ projects.add(p)
59
+ })
60
+ return projects
61
+ }, new Set<string>()),
62
+ ]
63
+ .map((p) => {
64
+ const project = JSON.parse(fs.readFileSync(p, 'utf8'))
65
+ project.path = p
66
+ return project
67
+ })
68
+ .filter((p) => Boolean(p))
69
+
70
+ if (!projects.length) {
71
+ throw new Error(
72
+ 'No matching projects found, supplied filepaths: ' +
73
+ configuration.projects.join(', ')
74
+ )
75
+ }
76
+
77
+ const factoryParams = {
78
+ configuration,
79
+ logger: logger as unknown as Console,
80
+ }
81
+ const register = buildRegister(factoryParams)
82
+ const runners = buildRunners(factoryParams)
83
+ each(projects).describe(projectTitle, (project: Project) => {
84
+ const suites = project.suites.filter(
85
+ (suite) =>
86
+ suite.tests.length && new RegExp(configuration.filter).test(suite.name)
87
+ )
88
+ if (suites.length) {
89
+ each(suites).describe(suiteTitle, (suite: SuiteShape) => {
90
+ const isParallel = suite.parallel
91
+ const tests = suite.tests
92
+ .map((tID) => register.test(project, tID))
93
+ .filter((test) => new RegExp(configuration.filter).test(test.name))
94
+ const testExecutor = each(tests)
95
+ const testMethod = isParallel
96
+ ? testExecutor.test.concurrent
97
+ : testExecutor.test
98
+ testMethod(testTitle, async (test: TestShape) => {
99
+ await runners.test(project, test)
100
+ })
101
+ })
102
+ } else {
103
+ console.info(`
104
+ Project ${project.name} doesn't have any suites matching filter ${configuration.filter},
105
+ attempting to iterate all tests in project
106
+ `)
107
+ const tests = project.tests
108
+ .map((test) => register.test(project, test.id))
109
+ .filter((test) => new RegExp(configuration.filter).test(test.name))
110
+ if (!tests.length) {
111
+ throw new Error(
112
+ `No suites or tests found in project ${project.name} matching filter ${configuration.filter}, unable to test!`
113
+ )
114
+ }
115
+ const testExecutor = each(tests)
116
+ testExecutor.test(testTitle, async (test: TestShape) => {
117
+ await runners.test(project, test)
118
+ })
119
+ }
120
+ })
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,182 @@
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 { CommandShape, 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
+ ({ id, state }: PlaybackEventShapes['COMMAND_STATE_CHANGED']) => {
110
+ const cmd = test.commands.find((c) => c.id === id) as CommandShape
111
+ const niceString = [cmd.command, cmd.target, cmd.value]
112
+ .filter((v) => !!v)
113
+ .join('|')
114
+ logger.debug(`${state} ${niceString}`)
115
+ }
116
+ )
117
+ try {
118
+ await playback.play(test, {
119
+ startingCommandIndex: 0,
120
+ })
121
+ } catch (e) {
122
+ console.error(e)
123
+ playback.cleanup()
124
+ return reject(e)
125
+ }
126
+ })
127
+ }
128
+ }
129
+
130
+ const runSuite = async (project: Project, suite: SuiteShape) => {
131
+ logger.info(`Running suite ${suite.name}`)
132
+ if (!suite.tests.length) {
133
+ throw new Error(
134
+ `The suite ${suite.name} has no tests, add tests to the suite using the IDE.`
135
+ )
136
+ }
137
+ for (let i = 0, ii = suite.tests.length; i != ii; i++) {
138
+ await runTest(
139
+ project,
140
+ project.tests.find((t) => t.id === suite.tests[i]) as TestShape
141
+ )
142
+ }
143
+ logger.info(`Finished suite ${suite.name}`)
144
+ }
145
+
146
+ const runProject = async (project: Project) => {
147
+ logger.info(`Running project ${project.name}`)
148
+ if (!configuration.force) {
149
+ let warning = Satisfies(project.version, '2.0')
150
+ if (warning) {
151
+ logger.warn(warning)
152
+ }
153
+ } else {
154
+ logger.warn("--force is set, ignoring project's version")
155
+ }
156
+ if (!project.suites.length) {
157
+ throw new Error(
158
+ `The project ${project.name} has no test suites defined, create a suite using the IDE.`
159
+ )
160
+ }
161
+
162
+ for (let i = 0, ii = project.suites.length; i != ii; i++) {
163
+ await runSuite(project, project.suites[i])
164
+ }
165
+ logger.info(`Finished project ${project.name}`)
166
+ }
167
+
168
+ const runAll = async (projects: Project[]) => {
169
+ for (let i = 0, ii = projects.length; i != ii; i++) {
170
+ await runProject(projects[i])
171
+ }
172
+ }
173
+
174
+ return {
175
+ all: runAll,
176
+ project: runProject,
177
+ suite: runSuite,
178
+ test: runTest,
179
+ }
180
+ }
181
+
182
+ 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
+ }