├── 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 | ![](todo.png) 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 | --------------------------------------------------------------------------------