├── .npmrc ├── other ├── manual-releases.md ├── ox.png └── CODE_OF_CONDUCT.md ├── .prettierignore ├── tsconfig.build.json ├── .vscode ├── setting.json └── launch.json ├── tests ├── testcafe │ ├── .eslintrc │ ├── screen.ts │ ├── configure.ts │ ├── selectors.ts │ └── within.ts └── unit │ ├── selectors.test.ts │ └── __snapshots__ │ └── selectors.test.ts.snap ├── .testcaferc.json ├── .github ├── dependabot.yml ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md └── workflows │ └── testcafe-testing-library.yml ├── tsconfig.json ├── jest.config.js ├── test-app ├── page2.html └── index.html ├── .eslintrc ├── src ├── types.ts └── index.ts ├── LICENSE ├── package.json ├── .gitignore ├── .all-contributorsrc ├── CODE_OF_CONDUCT.md └── README.md /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /other/manual-releases.md: -------------------------------------------------------------------------------- 1 | Number of manual releases: 1 2 | -------------------------------------------------------------------------------- /other/ox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testing-library/testcafe-testing-library/HEAD/other/ox.png -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | package.json 2 | node_modules 3 | dist 4 | coverage 5 | .github 6 | CODE_OF_CONDUCT.md 7 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "declaration": true 5 | }, 6 | "exclude": ["tests", "dist"] 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/setting.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib", 3 | "files.insertFinalNewline": true, 4 | "editor.codeActionsOnSave": { 5 | "source.fixAll.eslint": true 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/testcafe/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["testcafe"], 3 | "extends": ["plugin:testcafe/recommended", "prettier"], 4 | "rules": { 5 | "@typescript-eslint/no-unused-expressions": "off", 6 | "jest/no-done-callback": "off" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.testcaferc.json: -------------------------------------------------------------------------------- 1 | { 2 | "clientScripts": [ 3 | { "module": "@testing-library/dom/dist/@testing-library/dom.umd.js" } 4 | ], 5 | "src": "tests/testcafe/**/*.ts", 6 | "browsers": ["chrome:headless", "firefox:headless"], 7 | "concurrency": 3 8 | } 9 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: / 5 | schedule: 6 | interval: daily 7 | 8 | - package-ecosystem: npm 9 | directory: / 10 | schedule: 11 | interval: daily 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "commonjs", 5 | "strict": true, 6 | "esModuleInterop": true, 7 | "declaration": true, 8 | "outDir": "./dist", 9 | "skipLibCheck": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | const { jest: jestConfig } = require("kcd-scripts/config"); 2 | 3 | const config = Object.assign(jestConfig, { 4 | roots: ["tests/unit"], 5 | testMatch: ["/**/*.test.ts"], 6 | transform: { 7 | ".ts": "ts-jest", 8 | }, 9 | }); 10 | 11 | module.exports = config; 12 | -------------------------------------------------------------------------------- /test-app/page2.html: -------------------------------------------------------------------------------- 1 |
second page
2 |
3 |
4 |

configure

5 | 10 |
11 |
12 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["testcafe"], 3 | "extends": [ 4 | "./node_modules/kcd-scripts/eslint.js", 5 | "plugin:testcafe/recommended", 6 | "prettier" 7 | ], 8 | "rules": { 9 | "testing-library/no-dom-import": "off", 10 | "@babel/new-cap": "off", 11 | "@babel/quotes": "off" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import { Config, BoundFunction, queries } from "@testing-library/dom"; 2 | 3 | export type Options = Pick; 4 | 5 | export type TestcafeBoundFunction = ( 6 | ...params: Parameters> 7 | ) => SelectorPromise; 8 | 9 | export type TestcafeBoundFunctions = { 10 | [P in keyof T]: TestcafeBoundFunction; 11 | }; 12 | 13 | export type QueryName = keyof typeof queries; 14 | export type WithinSelectors = TestcafeBoundFunctions; 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /tests/testcafe/screen.ts: -------------------------------------------------------------------------------- 1 | import { screen } from "../../src"; 2 | 3 | fixture`screen`.page`../../test-app/index.html`; 4 | 5 | test("getByPlaceHolderText", async (t) => { 6 | await t.typeText( 7 | screen.getByPlaceholderText("Placeholder Text"), 8 | "Hello Placeholder" 9 | ); 10 | }); 11 | 12 | test("getByText", async (t) => { 13 | await t.click(screen.getByText("getByText")); 14 | }); 15 | 16 | test("getByLabelText", async (t) => { 17 | await t.typeText( 18 | screen.getByLabelText("Label For Input Labelled By Id"), 19 | "Hello Input Labelled By Id" 20 | ); 21 | }); 22 | 23 | test("getByRole", async (t) => { 24 | await t.click( 25 | screen.getByRole("textbox", { name: "Label For Input Labelled By Id" }) 26 | ); 27 | }); 28 | -------------------------------------------------------------------------------- /tests/testcafe/configure.ts: -------------------------------------------------------------------------------- 1 | import { configure, configureOnce, screen } from "../../src"; 2 | 3 | fixture`configure`.clientScripts( 4 | configure({ testIdAttribute: "data-automation-id" }) 5 | ).page`../../test-app/index.html`; 6 | 7 | test("supports alternative testIdAttribute", async (t) => { 8 | await t.click(screen.getByTestId("image-with-random-alt-tag")); 9 | }); 10 | 11 | test("still works after browser page load", async (t) => { 12 | await t 13 | .click(screen.getByText("Go to Page 2")) 14 | .click(screen.getByTestId("page2-thing")) 15 | .expect(screen.getByText("second page").exists) 16 | .ok(); 17 | }); 18 | 19 | test("can be used standalone", async (t) => { 20 | await configureOnce({ testIdAttribute: "data-other-test-id" }); 21 | await t.click(screen.getByTestId("other-id")); 22 | }); 23 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch Program", 11 | "program": "${workspaceFolder}/lib/index.js" 12 | }, 13 | { 14 | "type": "node", 15 | "protocol": "inspector", 16 | "request": "launch", 17 | "name": "Launch test files with TestCafe", 18 | "program": "${workspaceRoot}/node_modules/testcafe/bin/testcafe.js", 19 | "args": ["chrome", "${relativeFile}"], 20 | "console": "integratedTerminal", 21 | "cwd": "${workspaceRoot}" 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /tests/unit/selectors.test.ts: -------------------------------------------------------------------------------- 1 | import { queries } from "@testing-library/dom"; 2 | import * as allExports from "../../src"; 3 | 4 | it("exports expected exports", () => { 5 | expect(allExports).toMatchObject(expect.any(Object)); 6 | 7 | expect(Object.keys(allExports)).toMatchSnapshot(); 8 | 9 | const { screen, ...selectors } = allExports; 10 | 11 | Object.keys(selectors).forEach((selector) => { 12 | expect(selectors[selector as keyof typeof selectors]).toBeInstanceOf( 13 | Function 14 | ); 15 | }); 16 | 17 | Object.keys(screen).forEach((selector) => { 18 | expect(screen[selector as keyof typeof screen]).toBeInstanceOf(Function); 19 | }); 20 | }); 21 | 22 | it("exports all dom-testing-library queries", () => { 23 | const { configureOnce, configure, within, screen, ...justSelectors } = 24 | allExports; 25 | expect(Object.keys(justSelectors).sort()).toEqual( 26 | Object.keys(queries).sort() 27 | ); 28 | 29 | expect(Object.keys(screen).sort()).toEqual(Object.keys(queries).sort()); 30 | }); 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Ben Monro 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 | -------------------------------------------------------------------------------- /.github/workflows/testcafe-testing-library.yml: -------------------------------------------------------------------------------- 1 | name: testcafe-testing-library 2 | on: 3 | push: 4 | branches: 5 | - "master" 6 | - "alpha" 7 | pull_request: 8 | 9 | jobs: 10 | test: 11 | name: "node ${{ matrix.node }} ${{matrix.browser}} ${{ matrix.os }} " 12 | runs-on: "${{ matrix.os }}" 13 | strategy: 14 | matrix: 15 | os: [ubuntu-latest] 16 | node: [14, 16, 18] 17 | browser: ["chrome:headless", "firefox:headless"] 18 | steps: 19 | - uses: actions/setup-node@v3 20 | with: 21 | node-version: ${{ matrix.node }} 22 | - uses: actions/checkout@v3 23 | - run: npm install 24 | - run: npm run validate 25 | - name: Run TestCafe Tests 26 | uses: DevExpress/testcafe-action@latest 27 | with: 28 | args: "${{ matrix.browser}} tests/testcafe" 29 | release: 30 | runs-on: ubuntu-latest 31 | needs: test 32 | steps: 33 | - uses: actions/setup-node@v3 34 | with: 35 | node-version: 18 36 | - uses: actions/checkout@v3 37 | - run: npm install 38 | - run: npm run build 39 | - run: ls -asl dist 40 | - run: npx semantic-release 41 | env: 42 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 43 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 44 | -------------------------------------------------------------------------------- /tests/unit/__snapshots__/selectors.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`exports expected exports 1`] = ` 4 | Array [ 5 | configureOnce, 6 | configure, 7 | within, 8 | getByLabelText, 9 | getAllByLabelText, 10 | queryByLabelText, 11 | queryAllByLabelText, 12 | findByLabelText, 13 | findAllByLabelText, 14 | getByPlaceholderText, 15 | getAllByPlaceholderText, 16 | queryByPlaceholderText, 17 | queryAllByPlaceholderText, 18 | findByPlaceholderText, 19 | findAllByPlaceholderText, 20 | getByText, 21 | getAllByText, 22 | queryByText, 23 | queryAllByText, 24 | findByText, 25 | findAllByText, 26 | getByAltText, 27 | getAllByAltText, 28 | queryByAltText, 29 | queryAllByAltText, 30 | findByAltText, 31 | findAllByAltText, 32 | getByTitle, 33 | getAllByTitle, 34 | queryByTitle, 35 | queryAllByTitle, 36 | findByTitle, 37 | findAllByTitle, 38 | getByDisplayValue, 39 | getAllByDisplayValue, 40 | queryByDisplayValue, 41 | queryAllByDisplayValue, 42 | findByDisplayValue, 43 | getByRole, 44 | getAllByRole, 45 | queryByRole, 46 | queryAllByRole, 47 | findByRole, 48 | findAllByRole, 49 | findAllByDisplayValue, 50 | getByTestId, 51 | getAllByTestId, 52 | queryByTestId, 53 | queryAllByTestId, 54 | findByTestId, 55 | findAllByTestId, 56 | screen, 57 | ] 58 | `; 59 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@testing-library/testcafe", 3 | "version": "1.0.1-semantically-released", 4 | "description": "", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "scripts": { 8 | "build": "tsc -p tsconfig.build.json", 9 | "lint": "kcd-scripts lint", 10 | "test:unit": "kcd-scripts test --no-watch --config=jest.config.js", 11 | "test:unit:monorepo": "cd tests/unit && kcd-scripts test --no-watch --config=../../jest.config.js", 12 | "test:testcafe": "testcafe --skip-js-errors", 13 | "format": "prettier . --write", 14 | "format:check": "prettier . --check", 15 | "validate": "kcd-scripts validate format:check,lint,test:unit:monorepo", 16 | "test": "npm-run-all --parallel test:unit test:unit:monorepo test:testcafe" 17 | }, 18 | "files": [ 19 | "dist" 20 | ], 21 | "keywords": [], 22 | "author": "", 23 | "license": "ISC", 24 | "dependencies": { 25 | "@testing-library/dom": "^8.19.0" 26 | }, 27 | "peerDependencies": { 28 | "testcafe": ">=2.0.0" 29 | }, 30 | "devDependencies": { 31 | "eslint": "^8.26.0", 32 | "eslint-config-prettier": "^8.5.0", 33 | "eslint-plugin-testcafe": "^0.2.1", 34 | "kcd-scripts": "^12.3.0", 35 | "npm-run-all": "^4.1.5", 36 | "prettier": "^2.7.1", 37 | "semantic-release": "^20.0.2", 38 | "testcafe": ">=2.0.0", 39 | "ts-jest": "^29.0.3", 40 | "typescript": "^5.2.2" 41 | }, 42 | "repository": { 43 | "type": "git", 44 | "url": "https://github.com/testing-library/testcafe-testing-library.git" 45 | }, 46 | "engines": { 47 | "node": ">=14", 48 | "npm": ">=6" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | 24 | # nyc test coverage 25 | .nyc_output 26 | 27 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 28 | .grunt 29 | 30 | # Bower dependency directory (https://bower.io/) 31 | bower_components 32 | 33 | # node-waf configuration 34 | .lock-wscript 35 | 36 | # Compiled binary addons (https://nodejs.org/api/addons.html) 37 | build/Release 38 | 39 | # Dependency directories 40 | node_modules/ 41 | jspm_packages/ 42 | 43 | # Optional npm cache directory 44 | .npm 45 | 46 | # Optional eslint cache 47 | .eslintcache 48 | 49 | # Optional REPL history 50 | .node_repl_history 51 | 52 | # Output of 'npm pack' 53 | *.tgz 54 | 55 | # Yarn Integrity file 56 | .yarn-integrity 57 | 58 | # dotenv environment variables file 59 | .env 60 | .env.test 61 | 62 | # parcel-bundler cache (https://parceljs.org/) 63 | .cache 64 | 65 | # next.js build output 66 | .next 67 | 68 | # nuxt.js build output 69 | .nuxt 70 | 71 | # vuepress build output 72 | .vuepress/dist 73 | 74 | # Serverless directories 75 | .serverless/ 76 | 77 | # FuseBox cache 78 | .fusebox/ 79 | 80 | # DynamoDB Local files 81 | .dynamodb/ 82 | dist 83 | ignorethis.txt 84 | 85 | # OSX 86 | .DS_Store 87 | yarn.lock 88 | -------------------------------------------------------------------------------- /tests/testcafe/selectors.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable testing-library/prefer-screen-queries */ 2 | import { 3 | getByText, 4 | getByPlaceholderText, 5 | getByLabelText, 6 | getByAltText, 7 | getByTestId, 8 | getAllByText, 9 | queryByText, 10 | queryAllByText, 11 | findByText, 12 | } from "../../src/"; 13 | 14 | fixture`selectors`.page`../../test-app/index.html`; 15 | 16 | test("getByPlaceHolderText", async (t) => { 17 | await t.typeText( 18 | getByPlaceholderText("Placeholder Text"), 19 | "Hello Placeholder" 20 | ); 21 | }); 22 | 23 | test("getByText", async (t) => { 24 | await t.click(getByText("getByText")); 25 | }); 26 | 27 | test("queryByText with timeout as property", async (t) => { 28 | await t.click(queryByText("Late content!").with({ timeout: 20000 })); 29 | }); 30 | 31 | test("getByLabelText", async (t) => { 32 | await t.typeText( 33 | getByLabelText("Label For Input Labelled By Id"), 34 | "Hello Input Labelled By Id" 35 | ); 36 | }); 37 | 38 | test("getByAltText", async (t) => { 39 | await t.click(getByAltText("Image Alt Text")); 40 | }); 41 | 42 | test("getByTestId", async (t) => { 43 | await t.click(getByTestId("image-with-random-alt-tag")); 44 | }); 45 | 46 | test("getAllByText", async (t) => { 47 | const chans = getAllByText(/^Jackie Chan/); 48 | const count = await chans.count; 49 | 50 | await t.expect(count).eql(2); 51 | 52 | await t.click(chans.nth(1)); 53 | await t.click(chans.nth(0)); 54 | }); 55 | 56 | test("queryAllByText", async (t) => { 57 | await t.expect(queryAllByText("Button Text").exists).ok(); 58 | await t.expect(queryAllByText("Non-existing Button Text").exists).notOk(); 59 | }); 60 | 61 | test("findByText async", async (t) => { 62 | await t.click(getByText("delayed")); 63 | await t.expect(findByText("updated button async").exists).ok(); 64 | }); 65 | 66 | test("still works after browser page load", async (t) => { 67 | await t 68 | .click(getByText("Go to Page 2")) 69 | .expect(getByText("second page").exists) 70 | .ok(); 71 | }); 72 | 73 | test("still works after reload", async (t) => { 74 | await t.eval(() => location.reload()); 75 | await t.expect(getByText("getByText").exists).ok(); 76 | }); 77 | -------------------------------------------------------------------------------- /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "README.md" 4 | ], 5 | "imageSize": 100, 6 | "commit": false, 7 | "contributors": [ 8 | { 9 | "login": "benmonro", 10 | "name": "Ben Monro", 11 | "avatar_url": "https://avatars3.githubusercontent.com/u/399236?v=4", 12 | "profile": "https://github.com/benmonro", 13 | "contributions": [ 14 | "doc", 15 | "code", 16 | "test", 17 | "infra", 18 | "ideas" 19 | ] 20 | }, 21 | { 22 | "login": "kentcdodds", 23 | "name": "Kent C. Dodds", 24 | "avatar_url": "https://avatars0.githubusercontent.com/u/1500684?v=4", 25 | "profile": "https://kentcdodds.com", 26 | "contributions": [ 27 | "infra", 28 | "ideas", 29 | "code" 30 | ] 31 | }, 32 | { 33 | "login": "miherlosev", 34 | "name": "Mikhail Losev", 35 | "avatar_url": "https://avatars2.githubusercontent.com/u/4133518?v=4", 36 | "profile": "https://twitter.com/miherlosev", 37 | "contributions": [ 38 | "code", 39 | "question" 40 | ] 41 | }, 42 | { 43 | "login": "vanhoofmaarten", 44 | "name": "Maarten Van Hoof", 45 | "avatar_url": "https://avatars1.githubusercontent.com/u/2543633?v=4", 46 | "profile": "http://mrtnvh.com", 47 | "contributions": [ 48 | "code" 49 | ] 50 | }, 51 | { 52 | "login": "dobogo", 53 | "name": "Katsuya Hino", 54 | "avatar_url": "https://avatars2.githubusercontent.com/u/1498117?v=4", 55 | "profile": "https://github.com/dobogo", 56 | "contributions": [ 57 | "code" 58 | ] 59 | }, 60 | { 61 | "login": "theinterned", 62 | "name": "Ned Schwartz", 63 | "avatar_url": "https://avatars2.githubusercontent.com/u/2694?v=4", 64 | "profile": "http://theinterned.net", 65 | "contributions": [ 66 | "doc" 67 | ] 68 | }, 69 | { 70 | "login": "Meemaw", 71 | "name": "Matej Šnuderl", 72 | "avatar_url": "https://avatars3.githubusercontent.com/u/8524109?v=4", 73 | "profile": "https://www.matej.snuderl.si/", 74 | "contributions": [ 75 | "code", 76 | "test" 77 | ] 78 | }, 79 | { 80 | "login": "timmak", 81 | "name": "Tim Pinington", 82 | "avatar_url": "https://avatars2.githubusercontent.com/u/24553?v=4", 83 | "profile": "https://github.com/timmak", 84 | "contributions": [ 85 | "code", 86 | "test" 87 | ] 88 | }, 89 | { 90 | "login": "nilshartmann", 91 | "name": "Nils Hartmann", 92 | "avatar_url": "https://avatars0.githubusercontent.com/u/435073?v=4", 93 | "profile": "https://nilshartmann.net", 94 | "contributions": [ 95 | "bug" 96 | ] 97 | } 98 | ], 99 | "contributorsPerLine": 7, 100 | "projectName": "testcafe-testing-library", 101 | "projectOwner": "testing-library", 102 | "repoType": "github", 103 | "repoHost": "https://github.com", 104 | "skipCi": true 105 | } 106 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at . All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /other/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | - Using welcoming and inclusive language 18 | - Being respectful of differing viewpoints and experiences 19 | - Gracefully accepting constructive criticism 20 | - Focusing on what is best for the community 21 | - Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | - The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | - Trolling, insulting/derogatory comments, and personal or political attacks 28 | - Public or private harassment 29 | - Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | - Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies within all project spaces, and it also applies when 49 | an individual is representing the project or its community in public spaces. 50 | Examples of representing a project or community include using an official 51 | project e-mail address, posting via an official social media account, or acting 52 | as an appointed representative at an online or offline event. Representation of 53 | a project may be further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /test-app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | testcafe-testing-library 8 | 20 | 21 | 22 | 23 |
24 | No auto-reload after changing this static HTML markup: click 25 | Run All Tests. 26 |
27 |
28 |

getByPlaceholderText

29 | 30 |
31 |
32 |

getByText

33 | 34 |
35 |

getByText within

36 | 37 |
38 |
39 |

getByText within

40 | 41 | text only in 2nd nested 42 | another thing only in 2nd nested 43 |
44 |
45 |
46 |

getByLabelText

47 | 48 | 53 |
54 |
55 |

getByAltText

56 | Image Alt Text 61 |
62 |
63 |

getByTestId

64 | 69 |
70 |
71 |

configure

72 | 77 |
78 |
79 |

configure standalone

80 | 81 | 86 |
87 |
88 |

getAllByText

89 | 90 | 91 |
92 |
93 | 98 |
99 |
100 |

navigate

101 | Go to Page 2 102 |
103 |
104 |
105 |
106 |
My Group
107 | 110 |
0
111 |
112 |
113 |
114 | 116 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /tests/testcafe/within.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable testing-library/prefer-screen-queries */ 2 | /* eslint-disable @typescript-eslint/await-thenable */ 3 | /* eslint-disable new-cap */ 4 | import { Selector } from "testcafe"; 5 | import { within, screen } from "../../src"; 6 | 7 | fixture`within`.page`../../test-app/index.html`; 8 | 9 | test("getByText within container", async (t) => { 10 | const { getByText } = await within("#nested"); 11 | await t 12 | .click(getByText("Button Text")) 13 | .expect(Selector("button").withExactText("Button Clicked").exists) 14 | .ok(); 15 | }); 16 | 17 | test("queryByPlaceholder doesn't find anything", async (t) => { 18 | const { queryByPlaceholderText } = await within("#nested"); 19 | 20 | await t.expect(queryByPlaceholderText("Placeholder Text").exists).notOk(); 21 | }); 22 | 23 | test("quotes in selector", async (t) => { 24 | const { getByText } = await within('div[id="nested"]'); 25 | 26 | await t 27 | .click(getByText("Button Text")) 28 | .expect(Selector("button").withExactText("Button Clicked").exists) 29 | .ok(); 30 | }); 31 | 32 | test("still works after browser page reload", async (t) => { 33 | const nested = await within("#nested"); 34 | await t.expect(nested.getByText("Button Text").exists).ok(); 35 | 36 | await t.eval(() => location.reload()); 37 | await t.expect(nested.getByText("Button Text").exists).ok(); 38 | }); 39 | 40 | test("works with nested selectors", async (t) => { 41 | await t 42 | .expect( 43 | within(screen.getByTestId("nested")).getByText("Button Text").exists 44 | ) 45 | .ok(); 46 | }); 47 | 48 | test('works with nested selector from "All" query with index - regex', async (t) => { 49 | const nestedDivs = screen.getAllByTestId(/nested/); 50 | await t.expect(nestedDivs.count).eql(2); 51 | 52 | const nested = within(nestedDivs.nth(1)); 53 | 54 | await t 55 | .expect(nested.getByText("Button Text").exists) 56 | .ok() 57 | .expect(nested.getByText("text only in 2nd nested").exists) 58 | .ok(); 59 | }); 60 | 61 | test('works with nested selector from "All" query with index - exact:false', async (t) => { 62 | const nestedDivs = screen.getAllByTestId("nested", { exact: false }); 63 | await t.expect(nestedDivs.count).eql(2); 64 | const nested = await within(nestedDivs.nth(0)); 65 | 66 | await t.expect(nested.getByText("Button Text").exists).ok(); 67 | }); 68 | 69 | test('works with nested selector from "All" query with index - function', async (t) => { 70 | const nestedDivs = screen.getAllByTestId((_content, element) => 71 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 72 | element!.getAttribute("data-testid")!.startsWith("nested") 73 | ); 74 | await t.expect(nestedDivs.count).eql(2); 75 | const nested = await within(nestedDivs.nth(0)); 76 | 77 | await t.expect(nested.getByText("Button Text").exists).ok(); 78 | }); 79 | 80 | test("works on a standard testcafe nested selector", async (t) => { 81 | const nested = Selector("#nested"); 82 | 83 | await t.expect(within(nested).getByText("Button Text").exists).ok(); 84 | }); 85 | 86 | test("should throw if invalid param", async (t) => { 87 | let didThrow = false; 88 | try { 89 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 90 | // @ts-expect-error 91 | await t.expect(within({ foo: "bar" }).getByText("baz").exists).ok(); 92 | // eslint-disable-next-line @typescript-eslint/no-implicit-any-catch 93 | } catch (e) { 94 | didThrow = true; 95 | } 96 | await t.expect(didThrow).ok(); 97 | }); 98 | 99 | test("should throw error if count > 1", async (t) => { 100 | const nestedDivs = screen.getAllByTestId(/nested/); 101 | 102 | await t.expect(nestedDivs.count).eql(2); 103 | let didThrow = false; 104 | try { 105 | await t.expect(within(nestedDivs).getByText("blah").exists); 106 | // eslint-disable-next-line @typescript-eslint/no-implicit-any-catch 107 | } catch (e) { 108 | didThrow = true; 109 | } 110 | await t.expect(didThrow).ok(); 111 | }); 112 | 113 | test("works with findBy queries", async (t) => { 114 | const group = screen.findByRole("group", { name: "My Group" }); 115 | 116 | await t 117 | .click(within(group).findByRole("button", { name: "Increase B" })) 118 | .expect(within(group).findByText("1").exists) 119 | .ok(); 120 | }); 121 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-implied-eval */ 2 | /* eslint-disable no-new-func */ 3 | /* eslint-disable new-cap */ 4 | import { ClientFunction, Selector } from "testcafe"; 5 | import { Matcher, queries } from "@testing-library/dom"; 6 | import { type Options, type QueryName, type WithinSelectors } from "./types"; 7 | 8 | declare global { 9 | interface Window { 10 | TestingLibraryDom: typeof queries; 11 | } 12 | } 13 | 14 | const SELECTOR_TYPE = Selector("")().constructor.name; 15 | 16 | const queryNames = Object.keys(queries) as QueryName[]; 17 | const withinSelectors = queryNames.reduce((acc, withinQueryName) => { 18 | return { 19 | ...acc, 20 | [withinQueryName]: new Function(` 21 | const els = arguments[0]; 22 | if(els.length > 1) { 23 | throw new Error("within() only works with a single element, found " + els.length); 24 | } 25 | const el = els[0]; 26 | const args = Array.from(arguments).slice(1); 27 | return window.TestingLibraryDom.within(el).${withinQueryName.replace( 28 | "find", 29 | "query" 30 | )}.apply(null, args); 31 | `), 32 | }; 33 | // eslint-disable-next-line 34 | }, {} as Record any>); 35 | 36 | export async function configureOnce(options: Partial) { 37 | const { content } = configure(options); 38 | await ClientFunction(new Function(content) as () => Function)(); 39 | } 40 | 41 | export function configure(options: Partial) { 42 | const configFunction = ` 43 | window.TestingLibraryDom.configure(${JSON.stringify(options)}); 44 | `; 45 | return { content: configFunction }; 46 | } 47 | 48 | const withWithinMethods = (selector: Selector) => { 49 | return selector.addCustomMethods(withinSelectors, { 50 | returnDOMNodes: true, 51 | }) as unknown as WithinSelectors; 52 | }; 53 | type SelectorArg = 54 | | string 55 | | Selector 56 | | SelectorPromise 57 | | (() => SelectorPromise); 58 | 59 | export function within(selector: SelectorArg): WithinSelectors { 60 | if (selector instanceof Function) { 61 | return within(selector()); 62 | } 63 | 64 | if (isSelector(selector)) { 65 | return withWithinMethods(selector); 66 | } else if (typeof selector === "string") { 67 | return within(Selector(selector)); 68 | } else { 69 | throw new Error( 70 | `"within" only accepts a query (getBy, queryBy, etc), string or testcafe Selector` 71 | ); 72 | } 73 | } 74 | 75 | function isSelector(sel: SelectorArg): sel is Selector { 76 | return sel.constructor.name === SELECTOR_TYPE; 77 | } 78 | 79 | const bindFunction = (queryName: T) => { 80 | const query = queryName.replace("find", "query") as T; 81 | return (matcher: Matcher, options?: Parameters<(typeof queries)[T]>[2]) => { 82 | return Selector( 83 | () => 84 | window.TestingLibraryDom[query](document.body, matcher, options) as 85 | | Node 86 | | Node[] 87 | | NodeList 88 | | HTMLCollection, 89 | { 90 | dependencies: { query, matcher, options }, 91 | } 92 | ); 93 | }; 94 | }; 95 | 96 | export const getByLabelText = bindFunction("getByLabelText"); 97 | export const getAllByLabelText = bindFunction("getAllByLabelText"); 98 | export const queryByLabelText = bindFunction("queryByLabelText"); 99 | export const queryAllByLabelText = bindFunction("queryAllByLabelText"); 100 | export const findByLabelText = bindFunction("findByLabelText"); 101 | export const findAllByLabelText = bindFunction("findAllByLabelText"); 102 | export const getByPlaceholderText = bindFunction("getByPlaceholderText"); 103 | export const getAllByPlaceholderText = bindFunction("getAllByPlaceholderText"); 104 | export const queryByPlaceholderText = bindFunction("queryByPlaceholderText"); 105 | export const queryAllByPlaceholderText = bindFunction( 106 | "queryAllByPlaceholderText" 107 | ); 108 | export const findByPlaceholderText = bindFunction("findByPlaceholderText"); 109 | export const findAllByPlaceholderText = bindFunction( 110 | "findAllByPlaceholderText" 111 | ); 112 | export const getByText = bindFunction("getByText"); 113 | export const getAllByText = bindFunction("getAllByText"); 114 | export const queryByText = bindFunction("queryByText"); 115 | export const queryAllByText = bindFunction("queryAllByText"); 116 | export const findByText = bindFunction("findByText"); 117 | export const findAllByText = bindFunction("findAllByText"); 118 | export const getByAltText = bindFunction("getByAltText"); 119 | export const getAllByAltText = bindFunction("getAllByAltText"); 120 | export const queryByAltText = bindFunction("queryByAltText"); 121 | export const queryAllByAltText = bindFunction("queryAllByAltText"); 122 | export const findByAltText = bindFunction("findByAltText"); 123 | export const findAllByAltText = bindFunction("findAllByAltText"); 124 | export const getByTitle = bindFunction("getByTitle"); 125 | export const getAllByTitle = bindFunction("getAllByTitle"); 126 | export const queryByTitle = bindFunction("queryByTitle"); 127 | export const queryAllByTitle = bindFunction("queryAllByTitle"); 128 | export const findByTitle = bindFunction("findByTitle"); 129 | export const findAllByTitle = bindFunction("findAllByTitle"); 130 | export const getByDisplayValue = bindFunction("getByDisplayValue"); 131 | export const getAllByDisplayValue = bindFunction("getAllByDisplayValue"); 132 | export const queryByDisplayValue = bindFunction("queryByDisplayValue"); 133 | export const queryAllByDisplayValue = bindFunction("queryAllByDisplayValue"); 134 | export const findByDisplayValue = bindFunction("findByDisplayValue"); 135 | export const getByRole = bindFunction("getByRole"); 136 | export const getAllByRole = bindFunction("getAllByRole"); 137 | export const queryByRole = bindFunction("queryByRole"); 138 | export const queryAllByRole = bindFunction("queryAllByRole"); 139 | export const findByRole = bindFunction("findByRole"); 140 | export const findAllByRole = bindFunction("findAllByRole"); 141 | export const findAllByDisplayValue = bindFunction("findAllByDisplayValue"); 142 | export const getByTestId = bindFunction("getByTestId"); 143 | export const getAllByTestId = bindFunction("getAllByTestId"); 144 | export const queryByTestId = bindFunction("queryByTestId"); 145 | export const queryAllByTestId = bindFunction("queryAllByTestId"); 146 | export const findByTestId = bindFunction("findByTestId"); 147 | export const findAllByTestId = bindFunction("findAllByTestId"); 148 | 149 | export const screen = { 150 | getByLabelText, 151 | getAllByLabelText, 152 | queryByLabelText, 153 | queryAllByLabelText, 154 | findByLabelText, 155 | findAllByLabelText, 156 | getByText, 157 | getAllByText, 158 | queryByText, 159 | queryAllByText, 160 | findByText, 161 | findAllByText, 162 | getByAltText, 163 | getByPlaceholderText, 164 | getAllByPlaceholderText, 165 | queryByPlaceholderText, 166 | queryAllByPlaceholderText, 167 | findByPlaceholderText, 168 | findAllByPlaceholderText, 169 | getAllByAltText, 170 | queryByAltText, 171 | queryAllByAltText, 172 | findByAltText, 173 | findAllByAltText, 174 | getByTitle, 175 | getAllByTitle, 176 | queryByTitle, 177 | queryAllByTitle, 178 | findByTitle, 179 | findAllByTitle, 180 | getByDisplayValue, 181 | getAllByDisplayValue, 182 | queryByDisplayValue, 183 | queryAllByDisplayValue, 184 | findByDisplayValue, 185 | getByRole, 186 | getAllByRole, 187 | queryByRole, 188 | queryAllByRole, 189 | findByRole, 190 | findAllByRole, 191 | findAllByDisplayValue, 192 | getByTestId, 193 | getAllByTestId, 194 | queryByTestId, 195 | queryAllByTestId, 196 | findByTestId, 197 | findAllByTestId, 198 | }; 199 | 200 | export * from "./types"; 201 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

testcafe-testing-library

3 | 4 | 5 | ox 6 | 7 | 8 | 9 |

Testcafe selectors and utilities that encourage good testing practices laid down by dom-testing-library.

10 | 11 | [**Read the docs**](https://testing-library.com/docs/testcafe-testing-library/intro) | [Edit the docs](https://github.com/alexkrolick/testing-library-docs) 12 | 13 |
14 | 15 |
16 | 17 | [![testcafe-testing-library](https://github.com/testing-library/testcafe-testing-library/workflows/testcafe-testing-library/badge.svg)](https://github.com/testing-library/testcafe-testing-library/actions?query=branch%3Amaster+workflow%3Atestcafe-testing-library) 18 | [![version][version-badge]][package] 19 | [![downloads][downloads-badge]][npmtrends] 20 | [![MIT License][license-badge]][license] 21 | [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) 22 | 23 | [![All Contributors](https://img.shields.io/badge/all_contributors-7-blue.svg?style=flat-square)](#contributors) 24 | [![PRs Welcome][prs-badge]][prs] 25 | [![Code of Conduct][coc-badge]][coc] 26 | [![Dependabot Status](https://api.dependabot.com/badges/status?host=github&repo=testing-library/testcafe-testing-library)](https://dependabot.com) 27 | [![Discord][discord-badge]][discord] 28 | 29 | [![Watch on GitHub][github-watch-badge]][github-watch] 30 | [![Star on GitHub][github-star-badge]][github-star] 31 | [![Tweet][twitter-badge]][twitter] 32 | 33 |
34 | 35 | TestingJavaScript.com Learn the smart, efficient way to test any JavaScript application. 36 | 37 |
38 | 39 | ## The problem 40 | 41 | You want to use [dom-testing-library](https://github.com/kentcdodds/dom-testing-library) methods in your [Testcafe][testcafe] tests. 42 | 43 | ## This solution 44 | 45 | This allows you to use all the useful [dom-testing-library](https://github.com/kentcdodds/dom-testing-library) methods in your tests. 46 | 47 | ## Table of Contents 48 | 49 | 50 | 51 | 52 | - [Installation](#installation) 53 | - [Usage](#usage) 54 | - [Other Solutions](#other-solutions) 55 | - [Contributors](#contributors) 56 | - [LICENSE](#license) 57 | 58 | 59 | 60 | ## Installation 61 | 62 | This module is distributed via [npm][npm] which is bundled with [node][node] and 63 | should be installed as one of your project's `devDependencies`: 64 | 65 | ``` 66 | npm install --save-dev @testing-library/testcafe 67 | ``` 68 | 69 | ## Usage 70 | 71 | [Usage Docs](https://testing-library.com/docs/testcafe-testing-library/intro#usage) 72 | 73 | ## Other Solutions 74 | 75 | I'm not aware of any, if you are please [make a pull request][prs] and add it 76 | here! 77 | 78 | ## Contributors 79 | 80 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 |

Ben Monro

📖 💻 ⚠️ 🚇 🤔

Kent C. Dodds

🚇 🤔 💻

Mikhail Losev

💻 💬

Maarten Van Hoof

💻

Katsuya Hino

💻

Ned Schwartz

📖

Matej Šnuderl

💻 ⚠️

Tim Pinington

💻 ⚠️

Nils Hartmann

🐛
100 | 101 | 102 | 103 | 104 | 105 | 106 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! 107 | 108 | ## LICENSE 109 | 110 | MIT 111 | 112 | [npm]: https://www.npmjs.com/ 113 | [node]: https://nodejs.org 114 | [coverage]: https://codecov.io/github/testing-library/testcafe-testing-library 115 | [version-badge]: https://img.shields.io/npm/v/@testing-library/testcafe.svg?style=flat-square 116 | [package]: https://www.npmjs.com/package/@testing-library/testcafe 117 | [downloads-badge]: https://img.shields.io/npm/dm/@testing-library/testcafe.svg?style=flat-square 118 | [npmtrends]: http://www.npmtrends.com/@testing-library/testcafe 119 | [license-badge]: https://img.shields.io/npm/l/@testing-library/testcafe.svg?style=flat-square 120 | [license]: https://github.com/testing-library/testcafe-testing-library/blob/master/LICENSE 121 | [prs-badge]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square 122 | [prs]: http://makeapullrequest.com 123 | [donate-badge]: https://img.shields.io/badge/$-support-green.svg?style=flat-square 124 | [coc-badge]: https://img.shields.io/badge/code%20of-conduct-ff69b4.svg?style=flat-square 125 | [coc]: https://github.com/testing-library/testcafe-testing-library/blob/master/other/CODE_OF_CONDUCT.md 126 | [github-watch-badge]: https://img.shields.io/github/watchers/testing-library/testcafe-testing-library.svg?style=social 127 | [github-watch]: https://github.com/testing-library/testcafe-testing-library/watchers 128 | [github-star-badge]: https://img.shields.io/github/stars/testing-library/testcafe-testing-library.svg?style=social 129 | [github-star]: https://github.com/testing-library/testcafe-testing-library/stargazers 130 | [twitter]: https://twitter.com/intent/tweet?text=Check%20out%20testcafe-testing-library%20by%20%40benmonro%20https%3A%2F%2Fgithub.com%2Ftesting-library%2Ftestcafe-testing-library%20%F0%9F%91%8D 131 | [twitter-badge]: https://img.shields.io/twitter/url/https/github.com/testing-library/testcafe-testing-library.svg?style=social 132 | [emojis]: https://github.com/benmonro/all-contributors#emoji-key 133 | [all-contributors]: https://github.com/all-contributors/all-contributors 134 | [dom-testing-library]: https://github.com/testing-library/dom-testing-library 135 | [testcafe]: https://devexpress.github.io/testcafe/ 136 | [discord-badge]: https://img.shields.io/discord/723559267868737556.svg?color=7389D8&labelColor=6A7EC2&logo=discord&logoColor=ffffff&style=flat-square 137 | [discord]: https://discord.gg/testing-library 138 | --------------------------------------------------------------------------------