├── todo.png
├── output
└── dummy.js
├── todomvc-tests
├── README.md
├── step-definitions
│ └── create-todos.steps.js
├── features
│ └── create-todos.feature
├── helpers
│ └── custom.helper.js
├── persist-todos_test.js
├── edit-todos_test.js
├── todo-mvc_test.js
├── mark-as-completed_test.js
├── create-todos_test.js
└── pages
│ └── todos.page.js
├── tsconfig.json
├── steps.d.ts
├── .github
└── workflows
│ ├── test.yml
│ └── npm-publish.yml
├── codecept.conf.js
├── codecept.testcafe.conf.js
├── codecept.puppeteer.conf.js
├── codecept.webdriver.conf.js
├── package.json
├── .gitignore
└── README.md
/todo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codeceptjs/examples/HEAD/todo.png
--------------------------------------------------------------------------------
/output/dummy.js:
--------------------------------------------------------------------------------
1 | // this file is required only for this dir to be created
2 |
--------------------------------------------------------------------------------
/todomvc-tests/README.md:
--------------------------------------------------------------------------------
1 | # Testing "Create TODOs" using page objects
2 |
3 | - Create single todo
4 | - Create multiple todos
--------------------------------------------------------------------------------
/todomvc-tests/step-definitions/create-todos.steps.js:
--------------------------------------------------------------------------------
1 | const { I, TodosPage } = inject();
2 |
3 | Given('I have an empty todo list', () => {
4 | TodosPage.goto()
5 | })
6 |
7 | When(/I create a todo (\d+)/, (todo) => {
8 | TodosPage.enterTodo(todo)
9 | })
10 |
11 | Then('I see the new todo on my list', () => {
12 | TodosPage.seeNumberOfTodos(1);
13 | })
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "ts-node": {
3 | "files": true
4 | },
5 | "compilerOptions": {
6 | "target": "es2018",
7 | "lib": ["es2018", "DOM"],
8 | "esModuleInterop": true,
9 | "module": "commonjs",
10 | "strictNullChecks": false,
11 | "types": ["codeceptjs", "node"],
12 | "declaration": true,
13 | "skipLibCheck": true,
14 | "moduleDetection": "force"
15 | },
16 | "exclude": ["node_modules"]
17 | }
18 |
--------------------------------------------------------------------------------
/steps.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | type TodosPage = typeof import('./todomvc-tests/pages/todos.page.js');
3 | type CustomHelper = import('./todomvc-tests/helpers/custom.helper.js');
4 |
5 | declare namespace CodeceptJS {
6 | interface SupportObject { I: I, current: any, TodosPage: TodosPage }
7 | interface Methods extends Playwright, REST, CustomHelper {}
8 | interface I extends WithTranslation {}
9 | namespace Translation {
10 | interface Actions {}
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/todomvc-tests/features/create-todos.feature:
--------------------------------------------------------------------------------
1 | Feature: Create Todos with BDD
2 |
3 | Scenario: Create a single todo item @bdd
4 | Given I have an empty todo list
5 | When I create a todo 123456
6 | Then I see the new todo on my list
7 |
8 | #Scenario: Create multiple todos @bdd
9 | # Given I have these todos on my list
10 | # | name |
11 | # | Milk |
12 | # | Butter |
13 | # | Bread |
14 | # When I add 2 more todos
15 | # Then I see 4 todos on my list
16 |
--------------------------------------------------------------------------------
/todomvc-tests/helpers/custom.helper.js:
--------------------------------------------------------------------------------
1 | let Helper = codecept_helper;
2 |
3 | const toString = sel => {
4 | if (typeof(sel) === 'string') return sel
5 | if (typeof(sel) === 'object') {
6 | return sel.css || sel.xpath
7 | }
8 | }
9 |
10 | class CustomHelper extends Helper {
11 |
12 | async hover(selector) {
13 | let client = this.helpers['Puppeteer'];
14 |
15 | await client.page.hover(toString(selector))
16 | }
17 |
18 | async typeText(text) {
19 | let client = this.helpers['Puppeteer'];
20 |
21 | await client.page.keyboard.type(text)
22 | }
23 |
24 | }
25 |
26 | module.exports = CustomHelper;
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Acceptance Tests
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | - main
8 | pull_request:
9 | branches:
10 | - '**'
11 |
12 | jobs:
13 | build:
14 |
15 | runs-on: ubuntu-latest
16 |
17 | strategy:
18 | matrix:
19 | node-version: [20.x]
20 |
21 | steps:
22 | - uses: actions/checkout@v3
23 | - name: Use Node.js ${{ matrix.node-version }}
24 | uses: actions/setup-node@v3
25 | with:
26 | node-version: ${{ matrix.node-version }}
27 | - name: Run tests
28 | run: |
29 | npm i && npx playwright install chromium
30 | npx codeceptjs run --steps
31 |
--------------------------------------------------------------------------------
/codecept.conf.js:
--------------------------------------------------------------------------------
1 | const { setHeadlessWhen } = require('@codeceptjs/configure');
2 |
3 | setHeadlessWhen(process.env.HEADLESS);
4 |
5 | exports.config = {
6 | tests: './todomvc-tests/**/*_test.js',
7 | output: './output',
8 | helpers: {
9 | Playwright: {
10 | url: 'http://localhost',
11 | waitForTimeout: 5000,
12 | show: false,
13 | },
14 |
15 | REST: {},
16 |
17 | CustomHelper: {
18 | require: './todomvc-tests/helpers/custom.helper.js'
19 | }
20 | },
21 |
22 | gherkin: {
23 | features: './todomvc-tests/features/*.feature',
24 | steps: [
25 | './todomvc-tests/step-definitions/create-todos.steps.js'
26 | ]
27 | },
28 |
29 | include: {
30 | TodosPage: './todomvc-tests/pages/todos.page.js'
31 | },
32 | bootstrap: null,
33 | mocha: {},
34 | name: 'codecept demo tests'
35 | }
36 |
--------------------------------------------------------------------------------
/codecept.testcafe.conf.js:
--------------------------------------------------------------------------------
1 | const { setHeadlessWhen } = require('@codeceptjs/configure');
2 |
3 | setHeadlessWhen(process.env.HEADLESS);
4 |
5 | exports.config = {
6 | tests: './todomvc-tests/**/*_test.js',
7 | output: './output',
8 | helpers: {
9 | TestCafe: {
10 | url: 'http://localhost',
11 | browser: 'chrome',
12 | show: true,
13 | },
14 |
15 | REST: {},
16 |
17 | CustomHelper: {
18 | require: './todomvc-tests/helpers/custom.helper.js'
19 | }
20 | },
21 |
22 | gherkin: {
23 | features: './todomvc-tests/features/*.feature',
24 | steps: [
25 | './todomvc-tests/step-definitions/create-todos.steps.js'
26 | ]
27 | },
28 |
29 | include: {
30 | TodosPage: './todomvc-tests/pages/todos.page.js'
31 | },
32 | bootstrap: null,
33 | mocha: {},
34 | name: 'codecept demo tests'
35 | }
36 |
--------------------------------------------------------------------------------
/codecept.puppeteer.conf.js:
--------------------------------------------------------------------------------
1 | const { setHeadlessWhen } = require('@codeceptjs/configure');
2 |
3 | setHeadlessWhen(process.env.HEADLESS);
4 |
5 | exports.config = {
6 | tests: './todomvc-tests/**/*_test.js',
7 | output: './output',
8 | helpers: {
9 | Puppeteer: {
10 | url: 'http://localhost',
11 | waitForTimeout: 5000,
12 | waitForNavigation: 'networkidle0',
13 | waitForAction: 0,
14 | show: true,
15 | },
16 |
17 | REST: {},
18 |
19 | CustomHelper: {
20 | require: './todomvc-tests/helpers/custom.helper.js'
21 | }
22 | },
23 |
24 | gherkin: {
25 | features: './todomvc-tests/features/*.feature',
26 | steps: [
27 | './todomvc-tests/step-definitions/create-todos.steps.js'
28 | ]
29 | },
30 |
31 | include: {
32 | TodosPage: './todomvc-tests/pages/todos.page.js'
33 | },
34 | bootstrap: null,
35 | mocha: {},
36 | name: 'codecept demo tests'
37 | }
38 |
--------------------------------------------------------------------------------
/todomvc-tests/persist-todos_test.js:
--------------------------------------------------------------------------------
1 | Feature('Persist Todos')
2 |
3 | Before(async ({ I, TodosPage }) => {
4 | I.say('Given I have some todos')
5 | I.clearCookie()
6 | TodosPage.goto()
7 |
8 | TodosPage.enterTodos([
9 | {title: 'foo', completed: false},
10 | {title: 'bar', completed: false},
11 | {title: 'baz', completed: false},
12 | {title: 'boom', completed: true},
13 | ])
14 | TodosPage.refresh()
15 | I.saveScreenshot('initial-todos.png')
16 | })
17 |
18 | Scenario.skip('Todos survive a page refresh @step-06', async ({ I, TodosPage }) => {
19 | I.say('And I marked the first as completed')
20 | await TodosPage.markNthAsCompleted(1)
21 |
22 | I.say('When I refresh the page')
23 | TodosPage.refresh()
24 |
25 | I.say('Then I still see the same todos')
26 | TodosPage.seeNumberOfTodos(4)
27 | await TodosPage.seeNthTodoEquals(1, 'foo')
28 |
29 | I.saveScreenshot('todos-survive-page-refresh.png')
30 | })
31 |
--------------------------------------------------------------------------------
/codecept.webdriver.conf.js:
--------------------------------------------------------------------------------
1 | const { setHeadlessWhen } = require('@codeceptjs/configure');
2 |
3 | setHeadlessWhen(process.env.HEADLESS);
4 |
5 | exports.config = {
6 | tests: './todomvc-tests/**/*_test.js',
7 | output: './output',
8 | helpers: {
9 | WebDriver: {
10 | url: 'http://localhost',
11 | browser: 'chrome',
12 | },
13 |
14 | REST: {},
15 |
16 | CustomHelper: {
17 | require: './todomvc-tests/helpers/custom.helper.js'
18 | }
19 | },
20 |
21 | gherkin: {
22 | features: './todomvc-tests/features/*.feature',
23 | steps: [
24 | './todomvc-tests/step-definitions/create-todos.steps.js'
25 | ]
26 | },
27 |
28 | include: {
29 | TodosPage: './todomvc-tests/pages/todos.page.js'
30 | },
31 |
32 | plugins: {
33 | wdio: {
34 | enabled: true,
35 | services: ['selenium-standalone']
36 | }
37 | },
38 |
39 | bootstrap: null,
40 | mocha: {},
41 | name: 'codecept demo tests'
42 | }
43 |
--------------------------------------------------------------------------------
/todomvc-tests/edit-todos_test.js:
--------------------------------------------------------------------------------
1 | Feature('Edit/Delete Todos @step-06')
2 |
3 | Before(async ({ I, TodosPage }) => {
4 | TodosPage.goto()
5 |
6 | TodosPage.enterTodo('foo')
7 | TodosPage.enterTodo('bar')
8 | TodosPage.enterTodo('baz')
9 | })
10 |
11 | Scenario.skip('Edited todo is saved on blur', async ({ I, TodosPage }) => {
12 | I.say('Given I have some todos')
13 |
14 | I.say('When I edit the first todo')
15 | await TodosPage.editNthTodo(1, 'boom')
16 |
17 | I.say('Then I see that the todo text has been changed')
18 | await TodosPage.seeNthTodoEquals(1, 'boom')
19 |
20 | I.saveScreenshot('edited-todo-saved-on-blur.png')
21 | })
22 |
23 | Scenario('Delete todos', async ({ I, TodosPage }) => {
24 | I.say('Given I have some todos')
25 | I.say('When I delete the first todo')
26 | TodosPage.deleteNthTodo(1)
27 |
28 | I.say('Then the todo should disappear from the list')
29 | TodosPage.seeNumberOfTodos(2)
30 | })
31 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@codeceptjs/examples",
3 | "version": "1.2.4",
4 | "main": "index.js",
5 | "scripts": {
6 | "test": "echo \"Error: no test specified\" && exit 1"
7 | },
8 | "keywords": [],
9 | "author": "kaflan, davert",
10 | "license": "ISC",
11 | "dependencies": {
12 | "ts-node": "10.9.2"
13 | },
14 | "repository": {
15 | "type": "git",
16 | "url": "git+https://github.com/codeceptjs/examples.git"
17 | },
18 | "bugs": {
19 | "url": "https://github.com/codeceptjs/examples.git/issues"
20 | },
21 | "homepage": "https://github.com/codeceptjs/examples.git#readme",
22 | "description": "",
23 | "devDependencies": {
24 | "@codeceptjs/configure": "^0.5.0",
25 | "@codeceptjs/ui": "^0.2.0",
26 | "@wdio/selenium-standalone-service": "^5.16.10",
27 | "codeceptjs": "3.5.12",
28 | "playwright": "1.41.2",
29 | "puppeteer": "^2.0.0",
30 | "testcafe": "^1.7.0",
31 | "webdriverio": "^5.16.11"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 | output
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (https://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 | jspm_packages/
38 |
39 | # TypeScript v1 declaration files
40 | typings/
41 |
42 | # Optional npm cache directory
43 | .npm
44 |
45 | # Optional eslint cache
46 | .eslintcache
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # Output of 'npm pack'
52 | *.tgz
53 |
54 | # Yarn Integrity file
55 | .yarn-integrity
56 |
57 | # dotenv environment variables file
58 | .env
59 |
60 | # next.js build output
61 | .next
62 |
63 | package-lock.json
64 | .idea
65 |
--------------------------------------------------------------------------------
/.github/workflows/npm-publish.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3 | name: Publish npm Package
4 |
5 | on:
6 | push:
7 | branches:
8 | - master
9 |
10 | jobs:
11 | publish-npm:
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: actions/checkout@v3
15 | - uses: actions/setup-node@v3
16 | with:
17 | node-version: 20
18 | registry-url: https://registry.npmjs.org/
19 | - run: git config --global user.name "GitHub CD bot"
20 | - run: git config --global user.email "github-cd-bot@example.com"
21 | - run: npx semantic-release
22 | env:
23 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
24 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
25 | # push the version changes to GitHub
26 | - run: git add package.json && git commit -m'update version' && git push
27 | env:
28 | # The secret is passed automatically. Nothing to configure.
29 | github-token: ${{ secrets.GITHUB_TOKEN }}
30 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This repo contains tests for TodoMVC application.
2 | Tests can be executed via different helpers.
3 |
4 | 
5 |
6 | # Installation
7 |
8 | This is a playground for your first steps in testing, so instead of installing it from NPM it is recommended to clone it from repo instead and then install the dependencies:
9 |
10 | ```
11 | git clone git@github.com:codecept-js/examples.git codeceptjs-examples && cd codeceptjs-examples && npm i
12 | ```
13 |
14 | This will install CodeceptJS with Puppeteer, WebdriverIO & TestCafe packages.
15 |
16 | # Running Tests
17 |
18 | The default helper is Playwright.
19 |
20 | ## Playwright
21 |
22 | Use `codecept.conf.js` to run tests with Playwright:
23 |
24 | ```
25 | npx codeceptjs run --steps
26 | ```
27 |
28 | ## Puppeteer
29 |
30 | Use `codecept.puppeteer.conf.js` to run tests with Puppeteer:
31 |
32 | ```
33 | npx codeceptjs run --steps -c codecept.puppeteer.conf.js
34 | ```
35 |
36 |
37 | ## WebdriverIO
38 |
39 | Use `codecept.webdriver.conf.js` to run tests with WebdriverIO in Chrome:
40 |
41 | ```
42 | npx codeceptjs run -c codecept.webdriver.conf.js --steps
43 | ```
44 |
45 | ## TestCafe
46 |
47 | Use `codecept.testcafe.conf.js` to run tests with TestCafe in Chrome:
48 |
49 | ```
50 | npx codeceptjs run -c codecept.testcafe.conf.js --steps
51 | ```
52 |
53 | ## Headless Mode
54 |
55 | Run tests in headless mode:
56 |
57 | ```
58 | HEADLESS=true npx codeceptjs run --steps
59 | ```
60 |
61 | ## Parallel Execution
62 |
63 | Run tests in parallel with 3 workers:
64 |
65 | ```
66 | npx codeceptjs run-workers 3
67 | ```
68 |
69 | ## Credits
70 |
71 | Created as part of codepress by Stefan Huber.
72 | Maintained by CodeceptJS Team.
73 |
74 | ## LICENSE
75 |
76 | MIT
77 |
--------------------------------------------------------------------------------
/todomvc-tests/todo-mvc_test.js:
--------------------------------------------------------------------------------
1 | Feature('codepress demo')
2 |
3 | Before(async ({ I }) => {
4 | I.amOnPage('https://todomvc.com/examples/react/dist/')
5 |
6 | I.say('Given I already have some todos')
7 | const todoItems = [
8 | {title: 'Create a cypress like runner for CodeceptJS', completed: false},
9 | {title: 'Make it even better than cypress', completed: false},
10 | ]
11 |
12 | I.executeScript(({ todoItems }) => {
13 | localStorage.setItem('todos-angularjs', JSON.stringify(todoItems));
14 | }, todoItems)
15 |
16 | I.refreshPage()
17 |
18 | I.executeScript(() => console.log('Some info'))
19 | I.executeScript(() => console.error('Some error'))
20 |
21 | // Just to to some rest request
22 | I.sendPostRequest('https://reqres.in/api/users', {
23 | name: 'John Shaft',
24 | job: 'Investigator',
25 | })
26 | I.sendGetRequest('https://reqres.in/api/users?page=2')
27 | I.sendGetRequest('https://reqres.in/api/unknown/2')
28 |
29 | I.waitForVisible('.new-todo')
30 | })
31 |
32 | Scenario('Create some todo items @smoke', async ({ I }) => {
33 | I.say('When I focus the todo field')
34 | I.click('.new-todo')
35 |
36 | I.say('Then I can add additional todos')
37 | I.fillField({ css: '.new-todo'}, 'Optimize Puppeteer support')
38 | I.pressKey('Enter')
39 |
40 | I.fillField({ css: '.new-todo'}, 'Add a web REPL')
41 | I.pressKey('Enter')
42 |
43 | I.fillField(locate('.new-todo').as('TODO Input'), 'Support Appium')
44 | I.pressKey('Enter')
45 |
46 | I.fillField({ css: '.new-todo'}, 'Become REALLY productive writing E2E Tests with codepress and CodeceptJS')
47 | I.pressKey('Enter')
48 |
49 | I.say('And I see them in the list')
50 | I.seeNumberOfVisibleElements('.todo-list li', 4)
51 | I.see('Optimize Puppeteer support', { css: 'li:nth-child(1) label'})
52 | I.dontSee('Nightmare', '.main')
53 |
54 | I.say('I complete a todo')
55 | I.click({ css: 'li:nth-child(1) .toggle'})
56 | I.seeElement('li:nth-child(1).completed')
57 |
58 | I.say('I mark all as completed')
59 | I.saveScreenshot('create-multiple-todo-items.png')
60 | })
61 |
--------------------------------------------------------------------------------
/todomvc-tests/mark-as-completed_test.js:
--------------------------------------------------------------------------------
1 | xFeature('Mark as completed/not completed @step-06')
2 |
3 | Before(async ({ I, TodosPage }) => {
4 | TodosPage.goto()
5 |
6 | TodosPage.enterTodo('foo')
7 | TodosPage.enterTodo('bar')
8 | TodosPage.enterTodo('baz')
9 | })
10 |
11 | /**
12 | * Happy Path tests
13 | */
14 | Scenario('Mark todos as completed', async ({ I, TodosPage }) => {
15 | I.say('Given I have some todos')
16 |
17 | I.say('When I mark the first one as completed')
18 | await TodosPage.markNthAsCompleted(1)
19 |
20 | I.say('Then I see that 2 todos are still active')
21 | TodosPage.filterActive()
22 | TodosPage.seeNumberOfTodos(2)
23 |
24 | I.say('And I see that 1 has been completed')
25 | TodosPage.filterCompleted()
26 | TodosPage.seeNumberOfTodos(1)
27 |
28 | I.saveScreenshot('mark-todos-as-completed.png')
29 | })
30 |
31 | Scenario.skip('Unmark completed todos', async ({ I, TodosPage }) => {
32 | I.say('Given I have some todos')
33 |
34 | I.say('And I mark the first one as completed')
35 | await TodosPage.markNthAsCompleted(1)
36 | TodosPage.markAllAsCompleted ()
37 |
38 | I.say('When I unmark the completed todo item')
39 |
40 | I.say('Then I see that 3 todos are still active')
41 | TodosPage.filterActive()
42 | TodosPage.seeNumberOfTodos(3)
43 |
44 | I.saveScreenshot('unmark-todos-as-completed.png')
45 | })
46 |
47 | Scenario.skip('Mark all todos as completed', async ({ I, TodosPage }) => {
48 | I.say('Given I have some todos')
49 |
50 | I.say('When I mark them all as completed')
51 | TodosPage.markAllAsCompleted()
52 |
53 | I.say('Then I see that all 3 are completed')
54 | TodosPage.filterCompleted()
55 | TodosPage.seeNumberOfTodos(3)
56 |
57 | I.saveScreenshot('mark-all-todos-as-completed.png')
58 | })
59 |
60 | Scenario('Clear completed todos', async ({ I, TodosPage }) => {
61 | I.say('Given I have some completed todos')
62 | TodosPage.markAllAsCompleted()
63 |
64 | I.say('When I clear all completed items')
65 | TodosPage.clearCompleted()
66 | TodosPage.seeNumberOfTodos(0)
67 |
68 | I.saveScreenshot('clear-completed-todos.png')
69 | })
70 |
71 |
--------------------------------------------------------------------------------
/todomvc-tests/create-todos_test.js:
--------------------------------------------------------------------------------
1 | const { I, TodosPage } = inject();
2 |
3 | Feature('@first Create Todos @step:06 @smoke @story:12345')
4 |
5 | Before(async () => {
6 | TodosPage.goto()
7 | });
8 |
9 | /**
10 | * Happy Path tests
11 | */
12 | Scenario('Create a new todo item', async () => {
13 | I.say('Given I have an empty todo list')
14 |
15 | I.say('When I create a todo "foo"')
16 | TodosPage.enterTodo('foo')
17 |
18 | I.say('Then I see the new todo on my list')
19 | TodosPage.seeNumberOfTodos(1)
20 |
21 | I.saveScreenshot('create-todo-item.png')
22 | });
23 |
24 | Scenario('Create multiple todo items', async () => {
25 | I.say('Given I have an empty todo list')
26 | I.say('When I create todos "foo", "bar" and "baz"')
27 | TodosPage.enterTodo('foo')
28 | TodosPage.enterTodo('bar')
29 | TodosPage.enterTodo('baz')
30 |
31 | I.say('Then I have these 3 todos on my list')
32 | TodosPage.seeNumberOfTodos(3)
33 |
34 | I.saveScreenshot('create-multiple-todo-items.png')
35 | })
36 |
37 | /**
38 | * Edge cases
39 | */
40 |
41 | const examples = new DataTable(['Todo Text', 'Result'])
42 | examples.add(['Todo with umlauts äöü', 'is in list'])
43 | examples.add(['Very loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong TooooooooooooooooooooooooooooooooooooooooDooooooooooooooo', 'is in list'])
44 |
45 | Data(examples).
46 | Scenario('Todos containing weird characters', async ({ I, current, TodosPage }) => {
47 | I.say('When I enter {Todo Text}')
48 | TodosPage.enterTodo(current['Todo Text'])
49 |
50 | I.say('Then I see {Result}')
51 | if (current['Result'] === 'is in list') {
52 | await TodosPage.seeNthTodoEquals (1, current['Todo Text'])
53 | }
54 | })
55 |
56 | Scenario('Text input field should be cleared after each item', async () => {
57 | I.say('Given I have an empty todo list')
58 | I.say('When I enter a new todo')
59 | TodosPage.enterTodo('foo')
60 |
61 | I.say('Then I see that the input field has been cleared')
62 | TodosPage.seeEmptyTodoInput()
63 | })
64 |
65 | Scenario('Text input should be trimmed', async () => {
66 | I.say('Given I have an empty todo list')
67 | I.say('When I enter a todo with whitespace around the text')
68 | TodosPage.enterTodo(' Todo with lots of whitespace around ')
69 |
70 | I.say('Then I see the trimmed text of the todo in the list')
71 | await TodosPage.seeNthTodoEquals(1, 'Todo with lots of whitespace around')
72 | })
73 |
74 |
75 | Scenario('New todos should be added to the bottom of the list', async () => {
76 | I.say('Given I added some todos')
77 | TodosPage.enterTodo('first')
78 | TodosPage.enterTodo('second')
79 | TodosPage.enterTodo('last')
80 |
81 | I.say('When I look at my todo list')
82 | I.say('Then I see the todos in the order in which I added them')
83 | await TodosPage.seeNthTodoEquals(1, 'first')
84 | })
85 |
86 |
87 | Scenario('Footer should be visible when adding TODOs', async () => {
88 | I.say('Given I am adding todos')
89 | TodosPage.seeFooter()
90 | I.say('When I add a todo')
91 | TodosPage.enterTodo('first')
92 | I.say('Then I always see the footer')
93 | TodosPage.seeFooter()
94 | })
95 |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/todomvc-tests/pages/todos.page.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert')
2 |
3 | const I = actor();
4 |
5 | // const nthTodoCheckbox = nth => `.todo-list li:nth-child(${nth}) > div > input` // ({ xpath: `(//*[contains(@class,"todo-list")]/li/div/input)[${nth}]`})
6 | // const nthTTodoDeleteButton = nth => `.todo-list li:nth-child(${nth}) > div > button` // ({ xpath: `(//*[contains(@class,"todo-list")]/li/div/button)[${nth}]`})
7 | // const nthTodoEditField = nth => `.todo-list li:nth-child(${nth}) > form > input` // ({ xpath: `(//*[contains(@class,"todo-list")]/li/form/input)[${nth}]`})
8 | // const nthTodoItem = nth => `.todo-list li:nth-child(${nth})` // ({ xpath: `(//*[contains(@class,"todo-list")]/li)[${nth}]`})
9 |
10 | const nthTodoCheckbox = nth => locate('div > input').inside(`.todo-list li:nth-child(${nth})`)
11 | const nthTTodoDeleteButton = nth => locate('div > button').inside(`.todo-list li:nth-child(${nth})`).as(`${nth}nth delete button`)
12 | const nthTodoEditField = nth => locate('form > input').inside(`.todo-list li:nth-child(${nth})`).as(`${nth}nth todo input`)
13 | const nthTodoItem = nth => locate('.todo-list li').at(nth).as(`${nth} todo item`)
14 |
15 | module.exports = {
16 | goto() {
17 | I.amOnPage('https://todomvc.com/examples/react/dist/')
18 | I.refreshPage()
19 | I.executeScript(() => sessionStorage.clear())
20 | I.executeScript(() => console.error('Boom!'))
21 | I.waitForVisible('.new-todo')
22 | },
23 |
24 | enterTodo(todo) {
25 | I.fillField('.new-todo', todo)
26 | I.pressKey('Enter')
27 | I.wait(2);
28 | },
29 |
30 | enterTodos(todoItems) {
31 | I.executeScript((todoItems) => {
32 | localStorage.setItem('todos-angularjs', JSON.stringify(todoItems));
33 | }, todoItems)
34 | },
35 |
36 | async markNthAsCompleted(nthTodo) {
37 | const classNames = await I.grabAttributeFrom(nthTodoItem(nthTodo), 'class')
38 | assert(classNames.indexOf('completed') < 0, 'Expected todo to be not already marked as completed')
39 | I.click(nthTodoCheckbox(nthTodo))
40 | },
41 |
42 | async unmarkNthAsCompleted(nthTodo) {
43 | const classNames = await I.grabAttributeFrom(nthTodoItem(nthTodo), 'class')
44 | assert(classNames.indexOf('completed') >= 0, 'Expected todo to be marked as completed')
45 | I.click(nthTodoCheckbox(nthTodo))
46 | },
47 |
48 | markAllAsCompleted() {
49 | I.click('label[for="toggle-all"')
50 | },
51 |
52 | clearCompleted() {
53 | I.click('button.clear-completed')
54 | },
55 |
56 | filterAll() {
57 | I.click(locate('.filters li').at(1))
58 | },
59 |
60 | filterActive() {
61 | I.click(locate('.filters li').at(2))
62 | },
63 |
64 | filterCompleted() {
65 | I.click(locate('.filters li').at(3))
66 | },
67 |
68 | editNthTodo(nthTodo, newTodoText) {
69 | I.doubleClick(nthTodoItem(nthTodo))
70 | I.fillField(nthTodoEditField(nthTodo), newTodoText)
71 | I.pressKey('Enter')
72 | },
73 |
74 | deleteNthTodo(nthTodo) {
75 | // Use a custom helper function to hover over an todo item
76 | I.moveCursorTo(`.todo-list li:nth-child(${nthTodo})`)
77 | I.click(nthTTodoDeleteButton(nthTodo))
78 | },
79 |
80 | refresh() {
81 | I.refreshPage()
82 | },
83 |
84 | async seeNthTodoEquals(nthTodo, todo) {
85 | let todos = await I.grabTextFrom('.todo-list li')
86 | if (typeof todos === 'string') {
87 | todos = [todos]
88 | }
89 |
90 | assert(todos[nthTodo - 1] === todo, `Expected "${todo}" but got "${todos[nthTodo - 1]}"`)
91 | return todos
92 | },
93 |
94 | seeNumberOfTodos(numberOfTodos) {
95 | I.seeNumberOfVisibleElements('.todo-list li', numberOfTodos)
96 | },
97 |
98 | seeEmptyTodoInput() {
99 | I.seeInField('.new-todo', '')
100 | },
101 |
102 | seeFooter() {
103 | I.seeElement('footer.info')
104 | }
105 | }
106 |
107 |
108 |
--------------------------------------------------------------------------------