@wdio/cli 9.0.0 → 9.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/commands/config.d.ts.map +1 -1
- package/build/index.js +5 -3
- package/build/templates/exampleFiles/browser/Component.css.ejs +121 -0
- package/build/templates/exampleFiles/browser/Component.lit.ejs +154 -0
- package/build/templates/exampleFiles/browser/Component.lit.test.ejs +24 -0
- package/build/templates/exampleFiles/browser/Component.preact.ejs +28 -0
- package/build/templates/exampleFiles/browser/Component.preact.test.ejs +59 -0
- package/build/templates/exampleFiles/browser/Component.react.ejs +29 -0
- package/build/templates/exampleFiles/browser/Component.react.test.ejs +58 -0
- package/build/templates/exampleFiles/browser/Component.solid.ejs +28 -0
- package/build/templates/exampleFiles/browser/Component.solid.test.ejs +58 -0
- package/build/templates/exampleFiles/browser/Component.stencil.ejs +43 -0
- package/build/templates/exampleFiles/browser/Component.stencil.test.ejs +45 -0
- package/build/templates/exampleFiles/browser/Component.svelte.ejs +47 -0
- package/build/templates/exampleFiles/browser/Component.svelte.test.ejs +58 -0
- package/build/templates/exampleFiles/browser/Component.vue.ejs +34 -0
- package/build/templates/exampleFiles/browser/Component.vue.test.ejs +62 -0
- package/build/templates/exampleFiles/browser/standalone.test.ejs +13 -0
- package/build/templates/exampleFiles/cucumber/step_definitions/steps.js.ejs +55 -0
- package/build/templates/exampleFiles/mochaJasmine/test.e2e.js.ejs +11 -0
- package/build/templates/exampleFiles/pageobjects/login.page.js.ejs +45 -0
- package/build/templates/exampleFiles/pageobjects/page.js.ejs +17 -0
- package/build/templates/exampleFiles/pageobjects/secure.page.js.ejs +20 -0
- package/build/templates/exampleFiles/serenity-js/common/config/serenity.properties.ejs +1 -0
- package/build/templates/exampleFiles/serenity-js/common/serenity/github-api/GitHubStatus.ts.ejs +41 -0
- package/build/templates/exampleFiles/serenity-js/common/serenity/todo-list-app/TodoList.ts.ejs +100 -0
- package/build/templates/exampleFiles/serenity-js/common/serenity/todo-list-app/TodoListItem.ts.ejs +36 -0
- package/build/templates/exampleFiles/serenity-js/cucumber/step-definitions/steps.ts.ejs +37 -0
- package/build/templates/exampleFiles/serenity-js/cucumber/support/parameter.config.ts.ejs +18 -0
- package/build/templates/exampleFiles/serenity-js/cucumber/todo-list/completing_items.feature.ejs +23 -0
- package/build/templates/exampleFiles/serenity-js/cucumber/todo-list/narrative.md.ejs +17 -0
- package/build/templates/exampleFiles/serenity-js/jasmine/example.spec.ts.ejs +86 -0
- package/build/templates/exampleFiles/serenity-js/mocha/example.spec.ts.ejs +88 -0
- package/build/templates/snippets/afterTest.ejs +20 -0
- package/build/templates/snippets/capabilities.ejs +57 -0
- package/build/templates/snippets/cucumber.ejs +50 -0
- package/build/templates/snippets/electronTest.js.ejs +7 -0
- package/build/templates/snippets/jasmine.ejs +20 -0
- package/build/templates/snippets/macosTest.js.ejs +11 -0
- package/build/templates/snippets/mocha.ejs +14 -0
- package/build/templates/snippets/reporters.ejs +14 -0
- package/build/templates/snippets/serenity.ejs +18 -0
- package/build/templates/snippets/services.ejs +18 -0
- package/build/templates/snippets/testWithPO.js.ejs +22 -0
- package/build/templates/snippets/testWithoutPO.js.ejs +19 -0
- package/build/templates/snippets/vscodeTest.js.ejs +9 -0
- package/build/templates/wdio.conf.tpl.ejs +416 -0
- package/build/utils.d.ts.map +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { h } from '@stencil/core'
|
|
2
|
+
import { render } from '@wdio/browser-runner/stencil'
|
|
3
|
+
import { $, expect } from '@wdio/globals'
|
|
4
|
+
|
|
5
|
+
import { MyElement } from './Component.js'
|
|
6
|
+
|
|
7
|
+
describe('Stencil component testing', () => {
|
|
8
|
+
it('should increment value on click automatically', async () => {
|
|
9
|
+
await render({
|
|
10
|
+
components: [MyElement],
|
|
11
|
+
autoApplyChanges: true,
|
|
12
|
+
template: () => (
|
|
13
|
+
<my-element count={42}>WebdriverIO Component Testing</my-element>
|
|
14
|
+
)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const button = await $('my-element').$('button')
|
|
18
|
+
await expect(button).toHaveText('count is 42')
|
|
19
|
+
|
|
20
|
+
await button.click()
|
|
21
|
+
await button.click()
|
|
22
|
+
|
|
23
|
+
await expect(button).toHaveText('count is 44')
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('should increment value on click after flush', async () => {
|
|
27
|
+
const { flushAll } = await render({
|
|
28
|
+
components: [MyElement],
|
|
29
|
+
template: () => (
|
|
30
|
+
<my-element count={42}>WebdriverIO Component Testing</my-element>
|
|
31
|
+
)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
const button = await $('my-element').$('button')
|
|
35
|
+
await expect(button).toHaveText('count is 42')
|
|
36
|
+
|
|
37
|
+
await button.click()
|
|
38
|
+
await button.click()
|
|
39
|
+
flushAll()
|
|
40
|
+
|
|
41
|
+
await expect(button).toHaveText('count is 44')<%-
|
|
42
|
+
answers.includeVisualTesting ? `
|
|
43
|
+
await expect(button).toMatchElementSnapshot('counterButton')` : '' %>
|
|
44
|
+
})
|
|
45
|
+
})
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
let count = 0
|
|
3
|
+
const increment = () => {
|
|
4
|
+
count += 1
|
|
5
|
+
}
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<main id="root">
|
|
9
|
+
<div>
|
|
10
|
+
<a href="https://webdriver.io/docs/component-testing" target="_blank">
|
|
11
|
+
<img src="https://webdriver.io/assets/images/robot-3677788dd63849c56aa5cb3f332b12d5.svg" className="logo"
|
|
12
|
+
alt="WebdriverIO logo" />
|
|
13
|
+
</a>
|
|
14
|
+
</div>
|
|
15
|
+
<h1>WebdriverIO Component Testing</h1>
|
|
16
|
+
|
|
17
|
+
<div class="card">
|
|
18
|
+
<button on:click={increment}>
|
|
19
|
+
count is {count}
|
|
20
|
+
</button>
|
|
21
|
+
<p>
|
|
22
|
+
Edit <code>src/Component.test.<%- answers.isUsingTypeScript ? `ts` : 'js' %></code> and save to test HMR
|
|
23
|
+
</p>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<p class="read-the-docs">
|
|
27
|
+
Click on the Vite and Svelte logos to learn more
|
|
28
|
+
</p>
|
|
29
|
+
</main>
|
|
30
|
+
|
|
31
|
+
<style>
|
|
32
|
+
.logo {
|
|
33
|
+
height: 6em;
|
|
34
|
+
padding: 1.5em;
|
|
35
|
+
will-change: filter;
|
|
36
|
+
transition: filter 300ms;
|
|
37
|
+
}
|
|
38
|
+
.logo:hover {
|
|
39
|
+
filter: drop-shadow(0 0 2em #646cffaa);
|
|
40
|
+
}
|
|
41
|
+
.logo.svelte:hover {
|
|
42
|
+
filter: drop-shadow(0 0 2em #ff3e00aa);
|
|
43
|
+
}
|
|
44
|
+
.read-the-docs {
|
|
45
|
+
color: #888;
|
|
46
|
+
}
|
|
47
|
+
</style>
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<%
|
|
2
|
+
const harnessImport = answers.installTestingLibrary
|
|
3
|
+
? `import { render, fireEvent } from '@testing-library/svelte'`
|
|
4
|
+
: ``
|
|
5
|
+
const renderCommand = answers.installTestingLibrary
|
|
6
|
+
? `render(ExampleComponent)`
|
|
7
|
+
: `new ExampleComponent({ target: container, props: {} })`
|
|
8
|
+
%>
|
|
9
|
+
import { $, expect } from '@wdio/globals'
|
|
10
|
+
<%- harnessImport %>
|
|
11
|
+
<% if (answers.installTestingLibrary) { %>
|
|
12
|
+
import * as matchers from '@testing-library/jest-dom/matchers'
|
|
13
|
+
expect.extend(matchers)
|
|
14
|
+
<% } %>
|
|
15
|
+
import ExampleComponent from './Component.svelte'
|
|
16
|
+
import './Component.css'
|
|
17
|
+
|
|
18
|
+
describe('Svelte Component Testing', () => {
|
|
19
|
+
<% if (answers.installTestingLibrary) { %>
|
|
20
|
+
it('should test component with Testing Library', async () => {
|
|
21
|
+
const { getByText } = render(ExampleComponent)
|
|
22
|
+
|
|
23
|
+
const component = getByText(/count is 0/i)
|
|
24
|
+
expect(component).toBeInTheDocument()
|
|
25
|
+
|
|
26
|
+
await fireEvent.click(component)
|
|
27
|
+
await fireEvent.click(component)
|
|
28
|
+
|
|
29
|
+
expect(getByText(/count is 2/i)).toBeInTheDocument()
|
|
30
|
+
})
|
|
31
|
+
<% } else { %>
|
|
32
|
+
let container<%- answers.isUsingTypeScript ? `: Element` : '' %>
|
|
33
|
+
|
|
34
|
+
beforeEach(() => {
|
|
35
|
+
container = document.createElement('div')
|
|
36
|
+
document.body.appendChild(container)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
afterEach(() => {
|
|
40
|
+
container?.remove()
|
|
41
|
+
})
|
|
42
|
+
<% } %>
|
|
43
|
+
|
|
44
|
+
it('should test component with WebdriverIO', async () => {
|
|
45
|
+
<%- renderCommand %>
|
|
46
|
+
|
|
47
|
+
const component = await $('button*=count is')
|
|
48
|
+
await expect(component).toBePresent()
|
|
49
|
+
await expect(component).toHaveText('count is 0')
|
|
50
|
+
|
|
51
|
+
await component.click()
|
|
52
|
+
await component.click()
|
|
53
|
+
|
|
54
|
+
await expect(component).toHaveText('count is 2')<%-
|
|
55
|
+
answers.includeVisualTesting ? `
|
|
56
|
+
await expect(component).toMatchElementSnapshot('counterButton')` : '' %>
|
|
57
|
+
})
|
|
58
|
+
})
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref } from 'vue'
|
|
3
|
+
|
|
4
|
+
defineProps<{ msg: string }>()
|
|
5
|
+
|
|
6
|
+
const count = ref(0)
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<template>
|
|
10
|
+
<div id="root">
|
|
11
|
+
<div>
|
|
12
|
+
<a href="https://webdriver.io/docs/component-testing" target="_blank">
|
|
13
|
+
<img src="https://webdriver.io/assets/images/robot-3677788dd63849c56aa5cb3f332b12d5.svg" className="logo"
|
|
14
|
+
alt="WebdriverIO logo" />
|
|
15
|
+
</a>
|
|
16
|
+
</div>
|
|
17
|
+
<h1>{{ msg }}</h1>
|
|
18
|
+
|
|
19
|
+
<div class="card">
|
|
20
|
+
<button type="button" @click="count++">count is {{ count }}</button>
|
|
21
|
+
<p>
|
|
22
|
+
Edit <code>src/Component.test.<%- answers.isUsingTypeScript ? `ts` : 'js' %></code> and save to test HMR
|
|
23
|
+
</p>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<p class="read-the-docs">Click on the WebdriverIO logo to learn more</p>
|
|
27
|
+
</div>
|
|
28
|
+
</template>
|
|
29
|
+
|
|
30
|
+
<style scoped>
|
|
31
|
+
.read-the-docs {
|
|
32
|
+
color: #888;
|
|
33
|
+
}
|
|
34
|
+
</style>
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
<%
|
|
2
|
+
const harnessImport = answers.installTestingLibrary
|
|
3
|
+
? `import { render, fireEvent } from '@testing-library/vue'`
|
|
4
|
+
: `import { createApp } from 'vue'`
|
|
5
|
+
const renderCommand = answers.installTestingLibrary
|
|
6
|
+
? `render(ExampleComponent, { props: { msg: 'WebdriverIO Component Testing' } })`
|
|
7
|
+
: `createApp(ExampleComponent, { msg: 'WebdriverIO Component Testing' }).mount(container)`
|
|
8
|
+
%>
|
|
9
|
+
import { $, expect } from '@wdio/globals'
|
|
10
|
+
<%- harnessImport %>
|
|
11
|
+
<% if (answers.installTestingLibrary) { %>
|
|
12
|
+
import * as matchers from '@testing-library/jest-dom/matchers'
|
|
13
|
+
expect.extend(matchers)
|
|
14
|
+
<% } %>
|
|
15
|
+
import ExampleComponent from './Component.vue'
|
|
16
|
+
import './Component.css'
|
|
17
|
+
|
|
18
|
+
describe('Vue Component Testing', () => {
|
|
19
|
+
<% if (answers.installTestingLibrary) { %>
|
|
20
|
+
it('should test component with Testing Library', async () => {
|
|
21
|
+
// The render method returns a collection of utilities to query your component.
|
|
22
|
+
const { getByText } = render(ExampleComponent, {
|
|
23
|
+
props: { msg: 'WebdriverIO Component Testing' }
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
const component = getByText(/count is 0/i)
|
|
27
|
+
expect(component).toBeInTheDocument()
|
|
28
|
+
|
|
29
|
+
await fireEvent.click(component)
|
|
30
|
+
await fireEvent.click(component)
|
|
31
|
+
|
|
32
|
+
expect(getByText(/count is 2/i)).toBeInTheDocument()
|
|
33
|
+
})
|
|
34
|
+
<% } else { %>
|
|
35
|
+
let container<%- answers.isUsingTypeScript ? `: Element` : '' %>
|
|
36
|
+
|
|
37
|
+
beforeEach(() => {
|
|
38
|
+
container = document.createElement('div')
|
|
39
|
+
container.setAttribute('id', 'app')
|
|
40
|
+
document.body.appendChild(container)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
afterEach(() => {
|
|
44
|
+
container?.remove()
|
|
45
|
+
})
|
|
46
|
+
<% } %>
|
|
47
|
+
|
|
48
|
+
it('should test component with WebdriverIO', async () => {
|
|
49
|
+
<%- renderCommand %>
|
|
50
|
+
|
|
51
|
+
const component = await $('button*=count is')
|
|
52
|
+
await expect(component).toBePresent()
|
|
53
|
+
await expect(component).toHaveText('count is 0')
|
|
54
|
+
|
|
55
|
+
await component.click()
|
|
56
|
+
await component.click()
|
|
57
|
+
|
|
58
|
+
await expect(component).toHaveText('count is 2')<%-
|
|
59
|
+
answers.includeVisualTesting ? `
|
|
60
|
+
await expect(component).toMatchElementSnapshot('counterButton')` : '' %>
|
|
61
|
+
})
|
|
62
|
+
})
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { $, expect } from '@wdio/globals'
|
|
2
|
+
|
|
3
|
+
describe('WebdriverIO Component Testing', () => {
|
|
4
|
+
it('should be able to render to the DOM and assert', async () => {
|
|
5
|
+
const component = document.createElement('button')
|
|
6
|
+
component.innerHTML = 'Hello World!'
|
|
7
|
+
document.body.appendChild(component)
|
|
8
|
+
|
|
9
|
+
await expect($('aria/Hello World!')).toBePresent()
|
|
10
|
+
component.remove()
|
|
11
|
+
await expect($('aria/Hello World!')).not.toBePresent()
|
|
12
|
+
})
|
|
13
|
+
})
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
<%- answers.isUsingTypeScript || answers.esmSupport
|
|
2
|
+
? "import { Given, When, Then } from '@wdio/cucumber-framework';"
|
|
3
|
+
: "const { Given, When, Then } = require('@wdio/cucumber-framework');" %>
|
|
4
|
+
<%- answers.isUsingTypeScript || answers.esmSupport
|
|
5
|
+
? `import { expect, $ } from '@wdio/globals'`
|
|
6
|
+
: `const { expect, $ } = require('@wdio/globals')` %>
|
|
7
|
+
<%
|
|
8
|
+
/**
|
|
9
|
+
* step definition without page objects
|
|
10
|
+
*/
|
|
11
|
+
if (answers.usePageObjects) { %>
|
|
12
|
+
<%- answers.isUsingTypeScript || answers.esmSupport
|
|
13
|
+
? `import LoginPage from '${answers.relativePath}/login.page${answers.esmSupport ? '.js' : ''}';`
|
|
14
|
+
: `const LoginPage = require('${answers.relativePath}/login.page');` %>
|
|
15
|
+
<%- answers.isUsingTypeScript || answers.esmSupport
|
|
16
|
+
? `import SecurePage from '${answers.relativePath}/secure.page${answers.esmSupport ? '.js' : ''}';`
|
|
17
|
+
: `const SecurePage = require('${answers.relativePath}/secure.page');` %>
|
|
18
|
+
|
|
19
|
+
const pages = {
|
|
20
|
+
login: LoginPage
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
Given(/^I am on the (\w+) page$/, async (page) => {
|
|
24
|
+
await pages[page].open()
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
When(/^I login with (\w+) and (.+)$/, async (username, password) => {
|
|
28
|
+
await LoginPage.login(username, password)
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
Then(/^I should see a flash message saying (.*)$/, async (message) => {
|
|
32
|
+
await expect(SecurePage.flashAlert).toBeExisting();
|
|
33
|
+
await expect(SecurePage.flashAlert).toHaveTextContaining(message);
|
|
34
|
+
});
|
|
35
|
+
<% } else {
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* step definition with page objects
|
|
39
|
+
*/
|
|
40
|
+
%>
|
|
41
|
+
Given(/^I am on the (\w+) page$/, async (page) => {
|
|
42
|
+
await browser.url(`https://the-internet.herokuapp.com/${page}`);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
When(/^I login with (\w+) and (.+)$/, async (username, password) => {
|
|
46
|
+
await $('#username').setValue(username);
|
|
47
|
+
await $('#password').setValue(password);
|
|
48
|
+
await $('button[type="submit"]').click();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
Then(/^I should see a flash message saying (.*)$/, async (message) => {
|
|
52
|
+
await expect($('#flash')).toBeExisting();
|
|
53
|
+
await expect($('#flash')).toHaveTextContaining(message);
|
|
54
|
+
});
|
|
55
|
+
<% } %>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<% if (answers.purpose === 'vscode') {
|
|
2
|
+
%><%- include('../../snippets/vscodeTest.js.ejs', { answers }) %><%
|
|
3
|
+
} else if (answers.purpose === 'electron') {
|
|
4
|
+
%><%- include('../../snippets/electronTest.js.ejs', { answers }) %><%
|
|
5
|
+
} else if (answers.purpose === 'macos') {
|
|
6
|
+
%><%- include('../../snippets/macosTest.js.ejs', { answers }) %><%
|
|
7
|
+
} else if (answers.usePageObjects) {
|
|
8
|
+
%><%- include('../../snippets/testWithPO.js.ejs', { answers }) %><%
|
|
9
|
+
} else if (!answers.usePageObjects) {
|
|
10
|
+
%><%- include('../../snippets/testWithoutPO.js.ejs', { answers }) %><%
|
|
11
|
+
} %>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
<%- answers.isUsingTypeScript || answers.esmSupport
|
|
2
|
+
? `import { $ } from '@wdio/globals'`
|
|
3
|
+
: `const { $ } = require('@wdio/globals')` %>
|
|
4
|
+
<%- answers.isUsingTypeScript || answers.esmSupport
|
|
5
|
+
? `import Page from './page${answers.esmSupport ? '.js' : ''}';`
|
|
6
|
+
: "const Page = require('./page');" %>
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* sub page containing specific selectors and methods for a specific page
|
|
10
|
+
*/
|
|
11
|
+
class LoginPage extends Page {
|
|
12
|
+
/**
|
|
13
|
+
* define selectors using getter methods
|
|
14
|
+
*/
|
|
15
|
+
<%- answers.isUsingTypeScript ? "public " : "" %>get inputUsername () {
|
|
16
|
+
return $('#username');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
<%- answers.isUsingTypeScript ? "public " : "" %>get inputPassword () {
|
|
20
|
+
return $('#password');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
<%- answers.isUsingTypeScript ? "public " : "" %>get btnSubmit () {
|
|
24
|
+
return $('button[type="submit"]');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* a method to encapsule automation code to interact with the page
|
|
29
|
+
* e.g. to login using username and password
|
|
30
|
+
*/
|
|
31
|
+
<%- answers.isUsingTypeScript ? "public " : "" %>async login (username<%- answers.isUsingTypeScript ? ": string": "" %>, password<%- answers.isUsingTypeScript ? ": string": "" %>) {
|
|
32
|
+
await this.inputUsername.setValue(username);
|
|
33
|
+
await this.inputPassword.setValue(password);
|
|
34
|
+
await this.btnSubmit.click();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* overwrite specific options to adapt it to page object
|
|
39
|
+
*/
|
|
40
|
+
<%- answers.isUsingTypeScript ? "public " : "" %>open () {
|
|
41
|
+
return super.open('login');
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
<%- answers.isUsingTypeScript || answers.esmSupport ? "export default": "module.exports =" %> new LoginPage();
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<%- answers.isUsingTypeScript || answers.esmSupport
|
|
2
|
+
? `import { browser } from '@wdio/globals'`
|
|
3
|
+
: `const { browser } = require('@wdio/globals')` %>
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* main page object containing all methods, selectors and functionality
|
|
7
|
+
* that is shared across all page objects
|
|
8
|
+
*/
|
|
9
|
+
<%- answers.isUsingTypeScript || answers.esmSupport ? "export default" : "module.exports =" %> class Page {
|
|
10
|
+
/**
|
|
11
|
+
* Opens a sub page of the page
|
|
12
|
+
* @param path path of the sub page (e.g. /path/to/page.html)
|
|
13
|
+
*/
|
|
14
|
+
<%- answers.isUsingTypeScript ? "public " : "" %>open (path<%- answers.isUsingTypeScript ? ": string" : "" %>) {
|
|
15
|
+
return browser.url(`https://the-internet.herokuapp.com/${path}`)
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<%- answers.isUsingTypeScript || answers.esmSupport
|
|
2
|
+
? `import { $ } from '@wdio/globals'`
|
|
3
|
+
: `const { $ } = require('@wdio/globals')` %>
|
|
4
|
+
<%- answers.isUsingTypeScript || answers.esmSupport
|
|
5
|
+
? `import Page from './page${answers.esmSupport ? '.js' : ''}';`
|
|
6
|
+
: "const Page = require('./page');" %>
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* sub page containing specific selectors and methods for a specific page
|
|
10
|
+
*/
|
|
11
|
+
class SecurePage extends Page {
|
|
12
|
+
/**
|
|
13
|
+
* define selectors using getter methods
|
|
14
|
+
*/
|
|
15
|
+
<%- answers.isUsingTypeScript ? "public " : "" %>get flashAlert () {
|
|
16
|
+
return $('#flash');
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
<%- answers.isUsingTypeScript || answers.esmSupport ? "export default": "module.exports =" %> new SecurePage();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
serenity.project.name=<%= answers.projectName %>
|
package/build/templates/exampleFiles/serenity-js/common/serenity/github-api/GitHubStatus.ts.ejs
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<%- _.import('Ensure, equals', '@serenity-js/assertions' ) %>
|
|
2
|
+
<%- _.import('Task', '@serenity-js/core' ) %>
|
|
3
|
+
<%- _.import('GetRequest, LastResponse, Send', '@serenity-js/rest' ) %>
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Learn more about API testing with Serenity/JS
|
|
7
|
+
* https://serenity-js.org/handbook/api-testing/
|
|
8
|
+
*/
|
|
9
|
+
<%- _.export('class', 'GitHubStatus') %> {
|
|
10
|
+
static #baseApiUrl = 'https://www.githubstatus.com/api/v2/'
|
|
11
|
+
static #statusJson = this.#baseApiUrl + 'status.json'
|
|
12
|
+
|
|
13
|
+
static ensureAllSystemsOperational = () =>
|
|
14
|
+
Task.where(`#actor ensures all GitHub systems are operational`,
|
|
15
|
+
Send.a(GetRequest.to(this.#statusJson)),
|
|
16
|
+
Ensure.that(LastResponse.status(), equals(200)),
|
|
17
|
+
Ensure.that(
|
|
18
|
+
LastResponse.body<%- _.ifTs('<StatusResponse>') %>().status.description.describedAs('GitHub Status'),
|
|
19
|
+
equals('All Systems Operational')
|
|
20
|
+
),
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
<% if (_.useTypeScript) { %>
|
|
24
|
+
/**
|
|
25
|
+
* Interfaces describing a simplified response structure returned by the GitHub Status Summary API:
|
|
26
|
+
* https://www.githubstatus.com/api/v2/summary.json
|
|
27
|
+
*/
|
|
28
|
+
interface StatusResponse {
|
|
29
|
+
page: {
|
|
30
|
+
id: string
|
|
31
|
+
name: string
|
|
32
|
+
url: string
|
|
33
|
+
time_zone: string
|
|
34
|
+
updated_at: string
|
|
35
|
+
}
|
|
36
|
+
status: {
|
|
37
|
+
indicator: string
|
|
38
|
+
description: string
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
<% } %>
|
package/build/templates/exampleFiles/serenity-js/common/serenity/todo-list-app/TodoList.ts.ejs
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
<%- _.import('contain, Ensure, equals, includes, isGreaterThan', '@serenity-js/assertions') %>
|
|
2
|
+
<%- _.import('type Answerable, Check, d, type QuestionAdapter, Task, Wait', '@serenity-js/core') %>
|
|
3
|
+
<%- _.import('By, Enter, ExecuteScript, isVisible, Key, Navigate, Page, PageElement, PageElements, Press, Text', '@serenity-js/web') %>
|
|
4
|
+
|
|
5
|
+
<%- _.import('TodoListItem', './TodoListItem') %>
|
|
6
|
+
|
|
7
|
+
<%- _.export('class', 'TodoList') %> {
|
|
8
|
+
|
|
9
|
+
// Public API captures the business domain-focused tasks
|
|
10
|
+
// that an actor interacting with a TodoList app can perform
|
|
11
|
+
|
|
12
|
+
static createEmptyList = () =>
|
|
13
|
+
Task.where('#actor creates an empty todo list',
|
|
14
|
+
Navigate.to('https://todo-app.serenity-js.org/'),
|
|
15
|
+
Ensure.that(
|
|
16
|
+
Page.current().title().describedAs('website title'),
|
|
17
|
+
equals('Serenity/JS TodoApp'),
|
|
18
|
+
),
|
|
19
|
+
Wait.until(this.#newTodoInput(), isVisible()),
|
|
20
|
+
this.#emptyLocalStorageIfNeeded(),
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
static #emptyLocalStorageIfNeeded = () =>
|
|
24
|
+
Task.where('#actor empties local storage if needed',
|
|
25
|
+
Check.whether(this.#persistedItems().length, isGreaterThan(0))
|
|
26
|
+
.andIfSo(
|
|
27
|
+
this.#emptyLocalStorage(),
|
|
28
|
+
Page.current().reload(),
|
|
29
|
+
)
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
static createListContaining = (<%- _.param('itemNames', 'Array<Answerable<string>>') %>) =>
|
|
33
|
+
Task.where(`#actor starts with a list containing ${ itemNames.length } items`,
|
|
34
|
+
TodoList.createEmptyList(),
|
|
35
|
+
...itemNames.map(itemName => this.recordItem(itemName))
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
static recordItem = (<%- _.param('itemName', 'Answerable<string>') %>) =>
|
|
39
|
+
Task.where(d `#actor records an item called ${ itemName }`,
|
|
40
|
+
Enter.theValue(itemName).into(this.#newTodoInput()),
|
|
41
|
+
Press.the(Key.Enter).in(this.#newTodoInput()),
|
|
42
|
+
Wait.until(Text.ofAll(this.#items()), contain(itemName)),
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
static markAsCompleted = (<%- _.param('itemNames', 'Array<Answerable<string>>') %>) =>
|
|
46
|
+
Task.where(d`#actor marks the following items as completed: ${ itemNames }`,
|
|
47
|
+
...itemNames.map(itemName => TodoListItem.markAsCompleted(this.#itemCalled(itemName)))
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
static markAsOutstanding = (<%- _.param('itemNames', 'Array<Answerable<string>>') %>) =>
|
|
51
|
+
Task.where(d`#actor marks the following items as outstanding: ${ itemNames }`,
|
|
52
|
+
...itemNames.map(itemName => TodoListItem.markAsOutstanding(this.#itemCalled(itemName)))
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
static outstandingItemsCount = () =>
|
|
56
|
+
Text.of(PageElement.located(By.tagName('strong')).of(this.#outstandingItemsLabel()))
|
|
57
|
+
.as(Number)
|
|
58
|
+
.describedAs('number of items left')
|
|
59
|
+
|
|
60
|
+
// Private API captures ways to locate interactive elements and data transformation logic.
|
|
61
|
+
// Private API supports the public API and is not used in the test scenario directly.
|
|
62
|
+
|
|
63
|
+
static #itemCalled = (<%- _.param('name', 'Answerable<string>') %>) =>
|
|
64
|
+
this.#items()
|
|
65
|
+
.where(Text, includes(name))
|
|
66
|
+
.first()
|
|
67
|
+
.describedAs(d`an item called ${ name }`)
|
|
68
|
+
|
|
69
|
+
static #outstandingItemsLabel = () =>
|
|
70
|
+
PageElement.located(By.css('.todo-count'))
|
|
71
|
+
.describedAs('items left counter')
|
|
72
|
+
|
|
73
|
+
static #newTodoInput = () =>
|
|
74
|
+
PageElement.located(By.css('.new-todo'))
|
|
75
|
+
.describedAs('"What needs to be done?" input box')
|
|
76
|
+
|
|
77
|
+
static #items = () =>
|
|
78
|
+
PageElements.located(By.css('.todo-list li'))
|
|
79
|
+
.describedAs('displayed items')
|
|
80
|
+
|
|
81
|
+
static #persistedItems = () =>
|
|
82
|
+
Page.current()
|
|
83
|
+
.executeScript(`
|
|
84
|
+
return window.localStorage['serenity-js-todo-app']
|
|
85
|
+
? JSON.parse(window.localStorage['serenity-js-todo-app'])
|
|
86
|
+
: []
|
|
87
|
+
`).describedAs('persisted items')<%- _.ifTs(' as QuestionAdapter<PersistedTodoItem[]>') %>
|
|
88
|
+
|
|
89
|
+
static #emptyLocalStorage = () =>
|
|
90
|
+
Task.where('#actor empties local storage',
|
|
91
|
+
ExecuteScript.sync(`window.localStorage.removeItem('serenity-js-todo-app')`)
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
<% if (_.useTypeScript) { %>
|
|
95
|
+
interface PersistedTodoItem {
|
|
96
|
+
id: number;
|
|
97
|
+
name: string;
|
|
98
|
+
completed: boolean;
|
|
99
|
+
}
|
|
100
|
+
<% } %>
|
package/build/templates/exampleFiles/serenity-js/common/serenity/todo-list-app/TodoListItem.ts.ejs
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<%- _.import('contain, not', '@serenity-js/assertions') %>
|
|
2
|
+
<%- _.import('Check, d, type QuestionAdapter, Task', '@serenity-js/core') %>
|
|
3
|
+
<%- _.import('By, Click, CssClasses, PageElement', '@serenity-js/web') %>
|
|
4
|
+
|
|
5
|
+
<%- _.export('class', 'TodoListItem') %> {
|
|
6
|
+
|
|
7
|
+
// Public API captures the business domain-focused tasks
|
|
8
|
+
// that an actor interacting with a TodoListItem app can perform
|
|
9
|
+
|
|
10
|
+
static markAsCompleted = (<%- _.param('item', 'QuestionAdapter<PageElement>') %>) =>
|
|
11
|
+
Task.where(d `#actor marks ${ item } as completed`,
|
|
12
|
+
Check.whether(CssClasses.of(item), not(contain('completed')))
|
|
13
|
+
.andIfSo(this.toggle(item)),
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
static markAsOutstanding = (<%- _.param('item', 'QuestionAdapter<PageElement>') %>) =>
|
|
17
|
+
Task.where(d `#actor marks ${ item } as outstanding`,
|
|
18
|
+
Check.whether(CssClasses.of(item), contain('completed'))
|
|
19
|
+
.andIfSo(this.toggle(item)),
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
static toggle = (<%- _.param('item', 'QuestionAdapter<PageElement>') %>) =>
|
|
23
|
+
Task.where(d `#actor toggles the completion status of ${ item }`,
|
|
24
|
+
Click.on(
|
|
25
|
+
this.#toggleButton().of(item),
|
|
26
|
+
),
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
// Private API captures ways to locate interactive elements and data transformation logic.
|
|
30
|
+
// Private API supports the public API and is not used in the test scenario directly.
|
|
31
|
+
|
|
32
|
+
static #toggleButton = () =>
|
|
33
|
+
PageElement
|
|
34
|
+
.located(By.css('input.toggle'))
|
|
35
|
+
.describedAs('toggle button')
|
|
36
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<%- _.import('Given, When, Then, type DataTable', '@cucumber/cucumber') %>
|
|
2
|
+
<%- _.import('Ensure, equals', '@serenity-js/assertions') %>
|
|
3
|
+
<%- _.import('type Actor', '@serenity-js/core') %>
|
|
4
|
+
<%- _.import('TodoList', '../../serenity/todo-list-app/TodoList') %>
|
|
5
|
+
|
|
6
|
+
Given('{actor} starts/started with a list containing:', async (<%- _.param('actor', 'Actor') %>, <%- _.param('table', 'DataTable') %>) => {
|
|
7
|
+
await actor.attemptsTo(
|
|
8
|
+
TodoList.createListContaining(itemsFrom(table)),
|
|
9
|
+
)
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
When('{pronoun} marks/marked the following item(s) as completed:', async (<%- _.param('actor', 'Actor') %>, <%- _.param('table', 'DataTable') %>) => {
|
|
13
|
+
await actor.attemptsTo(
|
|
14
|
+
TodoList.markAsCompleted(itemsFrom(table)),
|
|
15
|
+
)
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
When('{pronoun} marks/marked the following item(s) as outstanding:', async (<%- _.param('actor', 'Actor') %>, <%- _.param('table', 'DataTable') %>) => {
|
|
19
|
+
await actor.attemptsTo(
|
|
20
|
+
TodoList.markAsOutstanding(itemsFrom(table)),
|
|
21
|
+
)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
Then('{pronoun} should see that she has {int} item(s) outstanding', async (<%- _.param('actor', 'Actor') %>, <%- _.param('expectedCount', 'number') %>) => {
|
|
25
|
+
await actor.attemptsTo(
|
|
26
|
+
Ensure.that(TodoList.outstandingItemsCount(), equals(expectedCount)),
|
|
27
|
+
)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Extracts the data from a single-column Cucumber DataTable and returns it as an `Array<string>`
|
|
32
|
+
*
|
|
33
|
+
* @param table
|
|
34
|
+
*/
|
|
35
|
+
function itemsFrom(<%- _.param('table', 'DataTable') %>)<%- _.returns('string[]') %> {
|
|
36
|
+
return table.raw().map(row => row[0])
|
|
37
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<%- _.import('defineParameterType', '@cucumber/cucumber') %>
|
|
2
|
+
<%- _.import('actorCalled, actorInTheSpotlight', '@serenity-js/core') %>
|
|
3
|
+
|
|
4
|
+
defineParameterType({
|
|
5
|
+
regexp: /[A-Z][a-z]+/,
|
|
6
|
+
transformer(<%- _.param('name', 'string') %>) {
|
|
7
|
+
return actorCalled(name)
|
|
8
|
+
},
|
|
9
|
+
name: 'actor',
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
defineParameterType({
|
|
13
|
+
regexp: /he|she|they|his|her|their/,
|
|
14
|
+
transformer() {
|
|
15
|
+
return actorInTheSpotlight()
|
|
16
|
+
},
|
|
17
|
+
name: 'pronoun',
|
|
18
|
+
})
|