├── .eslintrc
├── .github
└── workflows
│ └── test.yml
├── .gitignore
├── .prettierrc.js
├── LICENSE
├── README.md
├── package-lock.json
├── package.json
├── rollup.config.js
├── src
└── index.js
├── test.e2e.js
├── test.html
├── test.js
└── wdio.conf.js
/.eslintrc:
--------------------------------------------------------------------------------
1 | extends: eslint-config-riot
2 |
3 | globals:
4 | chai: true
5 | sinon: true
6 | sinonChai: true
7 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: test
2 |
3 | on:
4 | push:
5 | branches: [main, dev]
6 | pull_request:
7 | branches: [main, dev]
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 |
13 | strategy:
14 | matrix:
15 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
16 | node-version: [18.x, 20.x]
17 | steps:
18 | - uses: actions/checkout@v4
19 | - name: Local Unit Test ${{ matrix.node-version }}
20 | uses: actions/setup-node@v4
21 | with:
22 | node-version: ${{ matrix.node-version }}
23 | - run: npm i
24 | - run: npm test
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 |
6 | # Runtime data
7 | pids
8 | *.pid
9 | *.seed
10 |
11 | # Directory for instrumented libs generated by jscoverage/JSCover
12 | lib-cov
13 |
14 | # Coverage directory used by tools like istanbul
15 | coverage
16 |
17 | # nyc test coverage
18 | .nyc_output
19 |
20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
21 | .grunt
22 |
23 | # node-waf configuration
24 | .lock-wscript
25 |
26 | # Compiled binary addons (http://nodejs.org/api/addons.html)
27 | build/Release
28 |
29 | # Dependency directories
30 | node_modules
31 | jspm_packages
32 |
33 | # Optional npm cache directory
34 | .npm
35 |
36 | # Optional REPL history
37 | .node_repl_history
38 |
39 | # mac useless file
40 | .DS_Store
41 | /index.js
42 | /index.cjs
43 | coverage
44 | .idea
45 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | export { default } from '@riotjs/prettier-config'
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Gianluca Guarini
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # @riotjs/custom-elements
2 |
3 | [](https://github.com/riot/custom-elements/)
4 |
5 | Simple API to create vanilla custom elements with riot
6 |
7 | [![Build Status][ci-image]][ci-url]
8 | [![NPM version][npm-version-image]][npm-url]
9 | [![NPM downloads][npm-downloads-image]][npm-url]
10 | [![MIT License][license-image]][license-url]
11 |
12 | ## Demos
13 |
14 | - [Simple Demo](https://codesandbox.io/p/sandbox/riot-custom-elements-demo-forked-xqw2sq)
15 |
16 |
17 | ## Usage
18 |
19 | ```js
20 | import MyComponent from './my-component.riot'
21 | import define from '@riotjs/custom-elements'
22 |
23 | define('x-tag', MyComponent)
24 | ```
25 |
26 | Notice that in order to update the component properties via attribute you will need to rely on the [`observedAttributes`](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements) array
27 |
28 | ```html
29 |
30 |
31 | {props.message}
32 |
38 |
39 | ```
40 |
41 |
42 | [ci-image]:https://img.shields.io/github/actions/workflow/status/riot/custom-elements/test.yml?style=flat-square
43 | [ci-url]:https://github.com/riot/custom-elements/actions
44 |
45 | [license-image]:http://img.shields.io/badge/license-MIT-000000.svg?style=flat-square
46 | [license-url]:LICENSE
47 |
48 | [npm-version-image]:http://img.shields.io/npm/v/@riotjs/custom-elements.svg?style=flat-square
49 | [npm-downloads-image]:http://img.shields.io/npm/dm/@riotjs/custom-elements.svg?style=flat-square
50 | [npm-url]:https://npmjs.org/package/@riotjs/custom-elements
51 |
52 | ## API
53 |
54 | This module exports only a single factory function that is a wrapper around the native `customElements.define`. The `define` function accepts only 3 parameters:
55 |
56 | - tag name
57 | - tag api normally generated via riot compiler
58 | - custom options to pass to `customElements.define` like `{extends: 'button'}` for example
59 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@riotjs/custom-elements",
3 | "version": "9.0.1",
4 | "description": "Simple API to create vanilla custom elements with riot",
5 | "main": "index.js",
6 | "module": "index.js",
7 | "exports": {
8 | "import": "./index.js",
9 | "require": "./index.cjs"
10 | },
11 | "files": [
12 | "index.cjs",
13 | "index.js"
14 | ],
15 | "scripts": {
16 | "prepublishOnly": "npm run build && npm test",
17 | "lint": "npx eslint src test.js rollup.config.js",
18 | "build": "rollup -c",
19 | "pretest": "npm run build",
20 | "test": "npm run lint && npm run wdio",
21 | "wdio": "start-server-and-test 'npx serve' 3000 'wdio run ./wdio.conf.js'"
22 | },
23 | "type": "module",
24 | "repository": {
25 | "type": "git",
26 | "url": "git+https://github.com/riot/custom-elements.git"
27 | },
28 | "nyc": {
29 | "include": [
30 | "./index.next.js"
31 | ]
32 | },
33 | "keywords": [
34 | "es6",
35 | "riot",
36 | "custom elements",
37 | "webcomponents",
38 | "es2015"
39 | ],
40 | "author": "Gianluca Guarini (http://gianlucaguarini.com)",
41 | "license": "MIT",
42 | "bugs": {
43 | "url": "https://github.com/riot/custom-elements/issues"
44 | },
45 | "homepage": "https://github.com/riot/custom-elements#readme",
46 | "devDependencies": {
47 | "@riotjs/prettier-config": "^1.1.0",
48 | "@rollup/plugin-node-resolve": "^15.3.0",
49 | "@wdio/browser-runner": "^9.2.8",
50 | "@wdio/cli": "^9.2.8",
51 | "@wdio/mocha-framework": "^9.2.8",
52 | "chai": "^5.1.2",
53 | "eslint": "^8.55.0",
54 | "eslint-config-riot": "^4.1.1",
55 | "mocha": "^10.8.2",
56 | "prettier": "^3.3.3",
57 | "riot": "^9.4.3",
58 | "rollup": "^4.24.3",
59 | "serve": "^14.2.4",
60 | "sinon": "^19.0.2",
61 | "sinon-chai": "^4.0.0",
62 | "start-server-and-test": "^2.0.8"
63 | },
64 | "peerDependencies": {
65 | "riot": "^6.0.0 || ^7.0.0 || ^9.0.0"
66 | },
67 | "dependencies": {
68 | "@riotjs/util": "^2.4.0"
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import { nodeResolve } from '@rollup/plugin-node-resolve'
2 |
3 | const globals = {
4 | riot: 'riot',
5 | }
6 |
7 | export default {
8 | input: 'src/index.js',
9 | external: ['riot'],
10 | plugins: [nodeResolve()],
11 | output: [
12 | {
13 | file: 'index.cjs',
14 | name: 'riotCustomElements',
15 | format: 'umd',
16 | globals,
17 | },
18 | {
19 | name: 'riotCustomElements',
20 | file: 'index.js',
21 | format: 'es',
22 | globals,
23 | },
24 | ],
25 | }
26 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import { component } from 'riot'
2 | import { defineProperty } from '@riotjs/util/objects'
3 |
4 | /**
5 | * Create the style node to inject into the shadow DOM
6 | * @param {string} css - component css
7 | * @returns {HTMLElement} style DOM node
8 | */
9 | function createStyleNode(css) {
10 | const style = document.createElement('style')
11 | style.textContent = css
12 |
13 | return style
14 | }
15 |
16 | /**
17 | * Move all the child nodes from a source tag to another
18 | * @param {HTMLElement} source - source node
19 | * @param {HTMLElement} target - target node
20 | * @returns {undefined} it's a void method ¯\_(ツ)_/¯
21 | */
22 | function moveChildren(source, target) {
23 | if (source.firstChild) {
24 | target.appendChild(source.firstChild)
25 | moveChildren(source, target)
26 | }
27 | }
28 |
29 | /**
30 | * Create a new custom element Class using the riot core components
31 | * @param {Object} api - custom component api containing lifecycle methods and properties
32 | * @returns {Class} Class extends HTMLElement
33 | */
34 | export function createElementClass(api) {
35 | const { css, exports, template } = api
36 | const originalOnMounted = exports?.onMounted ?? (() => {})
37 | const tagImplementation = exports || {}
38 |
39 | return class extends HTMLElement {
40 | constructor() {
41 | // call the super generic HTMLElement class
42 | super()
43 | // create the shadow DOM
44 | this.shadow = this.attachShadow({ mode: 'open' })
45 | this.componentFactory = component({
46 | exports: { ...tagImplementation, onMounted: undefined },
47 | template,
48 | })
49 |
50 | // append the css if necessary
51 | if (css) this.shadow.appendChild(createStyleNode(css))
52 | }
53 |
54 | // on element appended callback
55 | connectedCallback() {
56 | this.component = this.componentFactory(this)
57 |
58 | // move the tag root html into the shadow DOM
59 | moveChildren(this.component.root, this.shadow)
60 |
61 | // call the onmounted only when the DOM has been moved
62 | originalOnMounted.apply(this.component, [
63 | this.component.props,
64 | this.component.state,
65 | ])
66 | }
67 |
68 | // on attribute changed
69 | attributeChangedCallback(attributeName, oldValue, newValue) {
70 | if (!this.component) return
71 |
72 | defineProperty(this.component, 'props', {
73 | ...this.component.props,
74 | [attributeName]: newValue,
75 | })
76 | this.component.update()
77 | }
78 |
79 | // on element removed
80 | disconnectedCallback() {
81 | this.component.unmount()
82 | }
83 |
84 | // component properties to observe
85 | static get observedAttributes() {
86 | return tagImplementation.observedAttributes || []
87 | }
88 | }
89 | }
90 |
91 | /**
92 | * Define a new custom element using the riot core components
93 | * @param {string} name - custom component tag name
94 | * @param {Object} api - custom component api containing lifecycle methods and properties
95 | * @param {Object} options - optional options that could be passed to customElements.define
96 | * @returns {undefined} it's a void method again ¯\_(ツ)_/¯
97 | */
98 | export default function define(name, api, options) {
99 | // define the new custom element
100 | return customElements.define(name, createElementClass(api), options)
101 | }
102 |
--------------------------------------------------------------------------------
/test.e2e.js:
--------------------------------------------------------------------------------
1 | import { browser, expect } from '@wdio/globals'
2 |
3 | describe('Run the mocha tests', function () {
4 | it('All the mocha tests passed', async () => {
5 | await browser.url('http://localhost:3000/test')
6 | await browser.waitUntil(async () =>
7 | browser.execute(() => typeof window.testFailures === 'number'),
8 | )
9 | const testFailures = await browser.execute(() => window.testFailures)
10 |
11 | expect(testFailures).toBe(0)
12 | })
13 | })
14 |
--------------------------------------------------------------------------------
/test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 | E2E Tests
11 |
12 |
22 |
23 |
24 |
25 |
26 |
29 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/test.js:
--------------------------------------------------------------------------------
1 | import { expect, use } from 'chai'
2 | import define from './index.js'
3 | import sinon from 'sinon'
4 | import sinonChai from 'sinon-chai'
5 |
6 | use(sinonChai)
7 |
8 | const tmpTagName = ((i = 0) => {
9 | return () => `tat-${i++}`
10 | })()
11 |
12 | describe('@riotjs/custom-elements', function () {
13 | it('can generate properly the tag options', () => {
14 | const name = tmpTagName()
15 | define(name, {
16 | template: (t, e) =>
17 | t('', [
18 | {
19 | selector: 'p',
20 | expressions: [
21 | {
22 | type: e.TEXT,
23 | childNodeIndex: 0,
24 | evaluate: (s) => s.props.message, // eslint-disable-line
25 | },
26 | ],
27 | },
28 | ]),
29 | exports: {
30 | observedAttributes: ['message'],
31 | },
32 | })
33 |
34 | const el = document.createElement(name)
35 | document.body.appendChild(el)
36 | el.setAttribute('message', 'hello')
37 | expect(el.component.props.message).to.be.equal('hello')
38 | })
39 |
40 | it('lifecycle events get properly called', () => {
41 | const name = tmpTagName()
42 | const onBeforeMount = sinon.spy()
43 | const onMounted = sinon.spy()
44 | const onBeforeUpdate = sinon.spy()
45 | const onUpdated = sinon.spy()
46 | const onBeforeUnmount = sinon.spy()
47 | const onUnmounted = sinon.spy()
48 |
49 | define(name, {
50 | template: (t, e) =>
51 | t('', [
52 | {
53 | selector: 'p',
54 | expressions: [
55 | {
56 | type: e.TEXT,
57 | childNodeIndex: 0,
58 | evaluate: (s) => s.props.message,
59 | },
60 | ],
61 | },
62 | ]),
63 | exports: {
64 | onBeforeMount,
65 | onMounted,
66 | onBeforeUpdate,
67 | onUpdated,
68 | onBeforeUnmount,
69 | onUnmounted,
70 | observedAttributes: ['message'],
71 | },
72 | })
73 |
74 | const el = document.createElement(name)
75 | document.body.appendChild(el)
76 |
77 | expect(onBeforeMount).to.have.been.calledOnce
78 | expect(onMounted).to.have.been.calledOnce
79 | expect(onBeforeUpdate).to.have.not.been.called
80 | expect(onUpdated).to.have.not.been.called
81 | expect(onBeforeUnmount).to.have.not.been.called
82 | expect(onUnmounted).to.have.not.been.called
83 |
84 | el.setAttribute('message', 'bar')
85 |
86 | expect(onBeforeMount).to.have.been.calledOnce
87 | expect(onMounted).to.have.been.calledOnce
88 | expect(onBeforeUpdate).to.have.been.calledOnce
89 | expect(onUpdated).to.have.been.calledOnce
90 | expect(onBeforeUnmount).to.have.not.been.called
91 | expect(onUnmounted).to.have.not.been.called
92 |
93 | el.parentNode.removeChild(el)
94 |
95 | expect(onBeforeMount).to.have.been.calledOnce
96 | expect(onMounted).to.have.been.calledOnce
97 | expect(onBeforeUpdate).to.have.been.calledOnce
98 | expect(onUpdated).to.have.been.calledOnce
99 | expect(onBeforeUnmount).to.have.been.calledOnce
100 | expect(onUnmounted).to.have.been.calledOnce
101 | })
102 |
103 | it('custom tag api methods will be properly created', () => {
104 | const name = tmpTagName()
105 | define(name, {
106 | template: (t, e) =>
107 | t('', [
108 | {
109 | selector: 'p',
110 | expressions: [
111 | {
112 | type: e.TEXT,
113 | childNodeIndex: 0,
114 | evaluate: (s) => s.props.message,
115 | },
116 | ],
117 | },
118 | ]),
119 | exports: {
120 | onClick() {
121 | this.foo = 'bar'
122 | },
123 | },
124 | })
125 |
126 | const el = document.createElement(name)
127 | document.body.appendChild(el)
128 | expect(el.component.onClick).to.be.ok
129 | })
130 |
131 | it('css will be properly created via shadow DOM', () => {
132 | const name = tmpTagName()
133 | define(name, {
134 | template: (t, e) =>
135 | t('', [
136 | {
137 | selector: 'p',
138 | expressions: [
139 | {
140 | type: e.TEXT,
141 | childNodeIndex: 0,
142 | evaluate: (s) => s.props.message,
143 | },
144 | ],
145 | },
146 | ]),
147 | css: ':host { margin: 10px; }',
148 | })
149 |
150 | const el = document.createElement(name)
151 | document.body.appendChild(el)
152 | expect(window.getComputedStyle(el).getPropertyValue('margin')).to.be.equal(
153 | '10px',
154 | )
155 | })
156 |
157 | it('the shadow root is accessible on the onmounted event (issue https://github.com/riot/custom-elements/issues/20)', () => {
158 | const name = tmpTagName()
159 | define(name, {
160 | template: (t) => t('', []),
161 | exports: {
162 | onMounted() {
163 | console.log(this)
164 | this.root.shadowRoot.querySelector('p').textContent = 'foo'
165 | },
166 | },
167 | })
168 |
169 | const el = document.createElement(name)
170 | document.body.appendChild(el)
171 | expect(el.shadowRoot.querySelector('p').textContent).to.be.equal('foo')
172 | })
173 | })
174 |
--------------------------------------------------------------------------------
/wdio.conf.js:
--------------------------------------------------------------------------------
1 | export const config = {
2 | //
3 | // ====================
4 | // Runner Configuration
5 | // ====================
6 | // WebdriverIO supports running e2e tests as well as unit and component tests.
7 | runner: 'local',
8 | //
9 | // ==================
10 | // Specify Test Files
11 | // ==================
12 | // Define which test specs should run. The pattern is relative to the directory
13 | // of the configuration file being run.
14 | //
15 | // The specs are defined as an array of spec files (optionally using wildcards
16 | // that will be expanded). The test for each spec file will be run in a separate
17 | // worker process. In order to have a group of spec files run in the same worker
18 | // process simply enclose them in an array within the specs array.
19 | //
20 | // The path of the spec files will be resolved relative from the directory of
21 | // of the config file unless it's absolute.
22 | //
23 | specs: ['./*.e2e.js'],
24 | // Patterns to exclude.
25 | exclude: [
26 | // 'path/to/excluded/files'
27 | ],
28 | //
29 | // ============
30 | // Capabilities
31 | // ============
32 | // Define your capabilities here. WebdriverIO can run multiple capabilities at the same
33 | // time. Depending on the number of capabilities, WebdriverIO launches several test
34 | // sessions. Within your capabilities you can overwrite the spec and exclude options in
35 | // order to group specific specs to a specific capability.
36 | //
37 | // First, you can define how many instances should be started at the same time. Let's
38 | // say you have 3 different capabilities (Chrome, Firefox, and Safari) and you have
39 | // set maxInstances to 1; wdio will spawn 3 processes. Therefore, if you have 10 spec
40 | // files and you set maxInstances to 10, all spec files will get tested at the same time
41 | // and 30 processes will get spawned. The property handles how many capabilities
42 | // from the same test should run tests.
43 | //
44 | maxInstances: 10,
45 | //
46 | // If you have trouble getting all important capabilities together, check out the
47 | // Sauce Labs platform configurator - a great tool to configure your capabilities:
48 | // https://saucelabs.com/platform/platform-configurator
49 | //
50 | capabilities: [
51 | {
52 | maxInstances: 5,
53 | browserName: 'chrome',
54 | acceptInsecureCerts: true,
55 | // We need to extends some Chrome flags in order to tell Chrome to run headless
56 | 'goog:chromeOptions': {
57 | args: ['--headless', '--disable-gpu', '--disable-dev-shm-usage'],
58 | },
59 | },
60 | ],
61 |
62 | //
63 | // ===================
64 | // Test Configurations
65 | // ===================
66 | // Define all options that are relevant for the WebdriverIO instance here
67 | //
68 | // Level of logging verbosity: trace | debug | info | warn | error | silent
69 | logLevel: 'info',
70 | //
71 | // Set specific log levels per logger
72 | // loggers:
73 | // - webdriver, webdriverio
74 | // - @wdio/browserstack-service, @wdio/lighthouse-service, @wdio/sauce-service
75 | // - @wdio/mocha-framework, @wdio/jasmine-framework
76 | // - @wdio/local-runner
77 | // - @wdio/sumologic-reporter
78 | // - @wdio/cli, @wdio/config, @wdio/utils
79 | // Level of logging verbosity: trace | debug | info | warn | error | silent
80 | // logLevels: {
81 | // webdriver: 'info',
82 | // '@wdio/appium-service': 'info'
83 | // },
84 | //
85 | // If you only want to run your tests until a specific amount of tests have failed use
86 | // bail (default is 0 - don't bail, run all tests).
87 | bail: 0,
88 | //
89 | // Set a base URL in order to shorten url command calls. If your `url` parameter starts
90 | // with `/`, the base url gets prepended, not including the path portion of your baseUrl.
91 | // If your `url` parameter starts without a scheme or `/` (like `some/path`), the base url
92 | // gets prepended directly.
93 | baseUrl: 'http://localhost',
94 | //
95 | // Default timeout for all waitFor* commands.
96 | waitforTimeout: 10000,
97 | //
98 | // Default timeout in milliseconds for request
99 | // if browser driver or grid doesn't send response
100 | connectionRetryTimeout: 120000,
101 | //
102 | // Default request retries count
103 | connectionRetryCount: 3,
104 | //
105 | // Test runner services
106 | // Services take over a specific job you don't want to take care of. They enhance
107 | // your test setup with almost no effort. Unlike plugins, they don't add new
108 | // commands. Instead, they hook themselves up into the test process.
109 | // services: [],
110 | //
111 | // Framework you want to run your specs with.
112 | // The following are supported: Mocha, Jasmine, and Cucumber
113 | // see also: https://webdriver.io/docs/frameworks
114 | //
115 | // Make sure you have the wdio adapter package for the specific framework installed
116 | // before running any tests.
117 | framework: 'mocha',
118 |
119 | //
120 | // The number of times to retry the entire specfile when it fails as a whole
121 | // specFileRetries: 1,
122 | //
123 | // Delay in seconds between the spec file retry attempts
124 | // specFileRetriesDelay: 0,
125 | //
126 | // Whether or not retried spec files should be retried immediately or deferred to the end of the queue
127 | // specFileRetriesDeferred: false,
128 | //
129 | // Test reporter for stdout.
130 | // The only one supported by default is 'dot'
131 | // see also: https://webdriver.io/docs/dot-reporter
132 | // reporters: ['spec'],
133 |
134 | // Options to be passed to Mocha.
135 | // See the full list at http://mochajs.org/
136 | mochaOpts: {
137 | ui: 'bdd',
138 | timeout: 60000,
139 | },
140 |
141 | //
142 | // =====
143 | // Hooks
144 | // =====
145 | // WebdriverIO provides several hooks you can use to interfere with the test process in order to enhance
146 | // it and to build services around it. You can either apply a single function or an array of
147 | // methods to it. If one of them returns with a promise, WebdriverIO will wait until that promise got
148 | // resolved to continue.
149 | /**
150 | * Gets executed once before all workers get launched.
151 | * @param {object} config wdio configuration object
152 | * @param {Array.