├── .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 |
31 |
32 | getByText
33 | Button Text
34 |
35 |
getByText within
36 | Button Text
37 |
38 |
39 |
getByText within
40 | Button Text
41 | text only in 2nd nested
42 | another thing only in 2nd nested
43 |
44 |
45 |
54 |
55 | getByAltText
56 |
61 |
62 |
63 | getByTestId
64 |
69 |
70 |
71 | configure
72 |
77 |
78 |
79 | configure standalone
80 |
81 |
86 |
87 |
88 | getAllByText
89 | Jackie Chan 1
90 | Jackie Chan 2
91 |
92 |
93 |
96 | delayed
97 |
98 |
99 |
103 |
104 |
105 |
106 |
My Group
107 |
108 | Increase B
109 |
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 |
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 | [](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 | [](https://github.com/semantic-release/semantic-release)
22 |
23 | [](#contributors)
24 | [![PRs Welcome][prs-badge]][prs]
25 | [![Code of Conduct][coc-badge]][coc]
26 | [](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 |
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 |
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 |
--------------------------------------------------------------------------------