├── .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 | [![Riot.js custom elements logo](https://raw.githubusercontent.com/riot/branding/main/custom-elements/custom-elements-horizontal.svg)](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.} capabilities list of capabilities details 153 | */ 154 | // onPrepare: function (config, capabilities) { 155 | // }, 156 | /** 157 | * Gets executed before a worker process is spawned and can be used to initialize specific service 158 | * for that worker as well as modify runtime environments in an async fashion. 159 | * @param {string} cid capability id (e.g 0-0) 160 | * @param {object} caps object containing capabilities for session that will be spawn in the worker 161 | * @param {object} specs specs to be run in the worker process 162 | * @param {object} args object that will be merged with the main configuration once worker is initialized 163 | * @param {object} execArgv list of string arguments passed to the worker process 164 | */ 165 | // onWorkerStart: function (cid, caps, specs, args, execArgv) { 166 | // }, 167 | /** 168 | * Gets executed just after a worker process has exited. 169 | * @param {string} cid capability id (e.g 0-0) 170 | * @param {number} exitCode 0 - success, 1 - fail 171 | * @param {object} specs specs to be run in the worker process 172 | * @param {number} retries number of retries used 173 | */ 174 | // onWorkerEnd: function (cid, exitCode, specs, retries) { 175 | // }, 176 | /** 177 | * Gets executed just before initialising the webdriver session and test framework. It allows you 178 | * to manipulate configurations depending on the capability or spec. 179 | * @param {object} config wdio configuration object 180 | * @param {Array.} capabilities list of capabilities details 181 | * @param {Array.} specs List of spec file paths that are to be run 182 | * @param {string} cid worker id (e.g. 0-0) 183 | */ 184 | // beforeSession: function (config, capabilities, specs, cid) { 185 | // }, 186 | /** 187 | * Gets executed before test execution begins. At this point you can access to all global 188 | * variables like `browser`. It is the perfect place to define custom commands. 189 | * @param {Array.} capabilities list of capabilities details 190 | * @param {Array.} specs List of spec file paths that are to be run 191 | * @param {object} browser instance of created browser/device session 192 | */ 193 | // before: function (capabilities, specs) { 194 | // }, 195 | /** 196 | * Runs before a WebdriverIO command gets executed. 197 | * @param {string} commandName hook command name 198 | * @param {Array} args arguments that command would receive 199 | */ 200 | // beforeCommand: function (commandName, args) { 201 | // }, 202 | /** 203 | * Hook that gets executed before the suite starts 204 | * @param {object} suite suite details 205 | */ 206 | // beforeSuite: function (suite) { 207 | // }, 208 | /** 209 | * Function to be executed before a test (in Mocha/Jasmine) starts. 210 | */ 211 | // beforeTest: function (test, context) { 212 | // }, 213 | /** 214 | * Hook that gets executed _before_ a hook within the suite starts (e.g. runs before calling 215 | * beforeEach in Mocha) 216 | */ 217 | // beforeHook: function (test, context, hookName) { 218 | // }, 219 | /** 220 | * Hook that gets executed _after_ a hook within the suite starts (e.g. runs after calling 221 | * afterEach in Mocha) 222 | */ 223 | // afterHook: function (test, context, { error, result, duration, passed, retries }, hookName) { 224 | // }, 225 | /** 226 | * Function to be executed after a test (in Mocha/Jasmine only) 227 | * @param {object} test test object 228 | * @param {object} context scope object the test was executed with 229 | * @param {Error} result.error error object in case the test fails, otherwise `undefined` 230 | * @param {*} result.result return object of test function 231 | * @param {number} result.duration duration of test 232 | * @param {boolean} result.passed true if test has passed, otherwise false 233 | * @param {object} result.retries information about spec related retries, e.g. `{ attempts: 0, limit: 0 }` 234 | */ 235 | // afterTest: function(test, context, { error, result, duration, passed, retries }) { 236 | // }, 237 | 238 | /** 239 | * Hook that gets executed after the suite has ended 240 | * @param {object} suite suite details 241 | */ 242 | // afterSuite: function (suite) { 243 | // }, 244 | /** 245 | * Runs after a WebdriverIO command gets executed 246 | * @param {string} commandName hook command name 247 | * @param {Array} args arguments that command would receive 248 | * @param {number} result 0 - command success, 1 - command error 249 | * @param {object} error error object if any 250 | */ 251 | // afterCommand: function (commandName, args, result, error) { 252 | // }, 253 | /** 254 | * Gets executed after all tests are done. You still have access to all global variables from 255 | * the test. 256 | * @param {number} result 0 - test pass, 1 - test fail 257 | * @param {Array.} capabilities list of capabilities details 258 | * @param {Array.} specs List of spec file paths that ran 259 | */ 260 | // after: function (result, capabilities, specs) { 261 | // }, 262 | /** 263 | * Gets executed right after terminating the webdriver session. 264 | * @param {object} config wdio configuration object 265 | * @param {Array.} capabilities list of capabilities details 266 | * @param {Array.} specs List of spec file paths that ran 267 | */ 268 | // afterSession: function (config, capabilities, specs) { 269 | // }, 270 | /** 271 | * Gets executed after all workers got shut down and the process is about to exit. An error 272 | * thrown in the onComplete hook will result in the test run failing. 273 | * @param {object} exitCode 0 - success, 1 - fail 274 | * @param {object} config wdio configuration object 275 | * @param {Array.} capabilities list of capabilities details 276 | * @param {} results object containing test results 277 | */ 278 | // onComplete: function(exitCode, config, capabilities, results) { 279 | // }, 280 | /** 281 | * Gets executed when a refresh happens. 282 | * @param {string} oldSessionId session ID of the old session 283 | * @param {string} newSessionId session ID of the new session 284 | */ 285 | // onReload: function(oldSessionId, newSessionId) { 286 | // } 287 | /** 288 | * Hook that gets executed before a WebdriverIO assertion happens. 289 | * @param {object} params information about the assertion to be executed 290 | */ 291 | // beforeAssertion: function(params) { 292 | // } 293 | /** 294 | * Hook that gets executed after a WebdriverIO assertion happened. 295 | * @param {object} params information about the assertion that was executed, including its results 296 | */ 297 | // afterAssertion: function(params) { 298 | // } 299 | } 300 | --------------------------------------------------------------------------------