├── .babelrc
├── .editoreconfig
├── .eslintrc
├── .gitattributes
├── .github
├── labels.yml
└── workflows
│ ├── check-security-alerts.yml
│ ├── handle-labels.yml
│ ├── handle-stale.yml
│ └── no-response.yml
├── .gitignore
├── .publishrc
├── LICENSE
├── README.md
├── appveyor.yml
├── lib
└── index.js
├── package.json
├── test
├── .eslintrc
├── data
│ ├── vue-js
│ │ └── index.html
│ ├── vue-loader
│ │ ├── components
│ │ │ ├── app.vue
│ │ │ ├── list-item.vue
│ │ │ └── list.vue
│ │ ├── index.html
│ │ └── main.js
│ └── vue-router
│ │ ├── components
│ │ ├── bar.vue
│ │ ├── foo.vue
│ │ ├── list-item.vue
│ │ └── list.vue
│ │ ├── index.html
│ │ └── main.js
├── vue-js.js
├── vue-loader.js
├── vue-router.js
└── without-vue.js
└── webpack.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["@babel/preset-env", { "loose": true }]
4 | ],
5 | "plugins": [
6 | "@babel/plugin-transform-runtime"
7 | ]
8 | }
9 |
--------------------------------------------------------------------------------
/.editoreconfig:
--------------------------------------------------------------------------------
1 | # This file is for unifying the coding style for different editors and IDEs
2 | # editorconfig.org
3 |
4 | root = true
5 |
6 | [*]
7 | end_of_line = lf
8 | charset = utf-8
9 | insert_final_newline = true
10 | trim_trailing_whitespace = true
11 | indent_style = space
12 | indent_size = 4
13 |
14 | [{.eslintrc,package.json,.travis.yml}]
15 | indent_size = 2
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "eslint:recommended",
3 | "parserOptions": {
4 | "ecmaVersion": 6,
5 | "sourceType": "module"
6 | },
7 | "rules": {
8 | "no-alert": 2,
9 | "no-array-constructor": 2,
10 | "no-caller": 2,
11 | "no-catch-shadow": 2,
12 | "no-console": 0,
13 | "no-eval": 2,
14 | "no-extend-native": 2,
15 | "no-extra-bind": 2,
16 | "no-implied-eval": 2,
17 | "no-iterator": 2,
18 | "no-label-var": 2,
19 | "no-labels": 2,
20 | "no-lone-blocks": 2,
21 | "no-loop-func": 2,
22 | "no-multi-str": 2,
23 | "no-native-reassign": 2,
24 | "no-new": 2,
25 | "no-new-func": 0,
26 | "no-new-object": 2,
27 | "no-new-wrappers": 2,
28 | "no-octal-escape": 2,
29 | "no-proto": 2,
30 | "no-return-assign": 2,
31 | "no-script-url": 2,
32 | "no-sequences": 2,
33 | "no-shadow": 2,
34 | "no-shadow-restricted-names": 2,
35 | "no-spaced-func": 2,
36 | "no-undef-init": 2,
37 | "no-unused-expressions": 2,
38 | "no-var": 2,
39 | "no-with": 2,
40 | "camelcase": 2,
41 | "comma-spacing": 2,
42 | "consistent-return": 2,
43 | "eqeqeq": 2,
44 | "semi": 2,
45 | "semi-spacing": [2, {"before": false, "after": true}],
46 | "space-infix-ops": 2,
47 | "space-unary-ops": [2, { "words": true, "nonwords": false }],
48 | "yoda": [2, "never"],
49 | "brace-style": [2, "stroustrup", { "allowSingleLine": false }],
50 | "eol-last": 2,
51 | "indent": 2,
52 | "key-spacing": [2, { "align": "value" }],
53 | "max-nested-callbacks": [2, 3],
54 | "new-parens": 2,
55 | "newline-after-var": [2, "always"],
56 | "no-lonely-if": 2,
57 | "no-multiple-empty-lines": [2, { "max": 2 }],
58 | "no-nested-ternary": 2,
59 | "no-underscore-dangle": 0,
60 | "no-unneeded-ternary": 2,
61 | "object-curly-spacing": [2, "always"],
62 | "operator-assignment": [2, "always"],
63 | "quotes": [2, "single", "avoid-escape"],
64 | "space-before-blocks": [2, "always"],
65 | "prefer-const": 2,
66 | "no-path-concat": 2,
67 | "no-undefined": 2,
68 | "keyword-spacing": 2,
69 | "strict": 0,
70 | "curly": [2, "multi-or-nest"],
71 | "dot-notation": 0,
72 | "no-else-return": 2,
73 | "one-var": [2, "never"],
74 | "no-multi-spaces": [2, {
75 | "exceptions": {
76 | "VariableDeclarator": true,
77 | "AssignmentExpression": true
78 | }
79 | }],
80 | "radix": 2,
81 | "no-extra-parens": 2,
82 | "new-cap": [2, { "capIsNew": false }],
83 | "space-before-function-paren": [2, "always"],
84 | "no-use-before-define" : [2, "nofunc"],
85 | "handle-callback-err": 0
86 | },
87 | "env": {
88 | "node": true,
89 | "browser": true
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
4 | # Custom for Visual Studio
5 | *.cs diff=csharp
6 |
7 | # Standard to msysgit
8 | *.doc diff=astextplain
9 | *.DOC diff=astextplain
10 | *.docx diff=astextplain
11 | *.DOCX diff=astextplain
12 | *.dot diff=astextplain
13 | *.DOT diff=astextplain
14 | *.pdf diff=astextplain
15 | *.PDF diff=astextplain
16 | *.rtf diff=astextplain
17 | *.RTF diff=astextplain
--------------------------------------------------------------------------------
/.github/labels.yml:
--------------------------------------------------------------------------------
1 | # EDITS SHOULD BE SUBMITTED TO DevExpress/testcafe-build-system/config/labels.yml
2 | # Configuration for Label Actions - https://github.com/dessant/label-actions
3 |
4 | ? 'TYPE: question'
5 | :
6 | # Post a comment
7 | comment: |
8 | Thank you for your inquiry. This issue looks like a question that would be best [asked on StackOverflow](https://stackoverflow.com/questions/ask?tags=testcafe) - an amazing platform for users to ask and answer questions. We try to keep the GitHub issues tracker for bugs and feature requests only (see [Contributing](https://github.com/DevExpress/testcafe#contributing)).
9 |
10 | If you think that this issue is a bug, feel free to [open a new issue](https://github.com/DevExpress/testcafe/issues/new?template=bug-report.md), but please make sure to follow the issue template. Thank you in advance.
11 | unlabel: 'STATE: Need response'
12 | close: true
13 |
14 | ? 'STATE: Non-latest version'
15 | :
16 | # Post a comment
17 | comment: |
18 | Thank you for submitting this issue. Since you are using an outdated version of TestCafe, we recommend you update TestCafe to the [latest version](https://github.com/DevExpress/testcafe/releases/latest) to check if this issue has been addressed. We constantly improve our tools, and there is a chance that this issue has been already resolved and/or is no longer reproducible in the latest version. We look forward to your response.
19 | label: 'STATE: Need clarification'
20 | unlabel:
21 | - 'STATE: Non-latest version'
22 | - 'STATE: Need response'
23 |
24 | ? 'STATE: Need simple sample'
25 | :
26 | # Post a comment
27 | comment: |
28 | Thank you for submitting this issue. We would love to assist you and diagnose it. However, we need a simple sample that we can easily run on our side in order to replicate the issue and research its cause. Without a sample, we are not able to figure out what's going on and why this issue occurs. Refer to this article to create the best example: [How To: Create a Minimal Working Example When You Submit an Issue](https://testcafe.io/402636/faq#how-to-create-a-minimal-working-example-when-you-submit-an-issue). We look forward to your response.
29 | label: 'STATE: Need clarification'
30 | unlabel:
31 | - 'STATE: Need simple sample'
32 | - 'STATE: Need response'
33 |
34 | ? 'STATE: Need access confirmation'
35 | :
36 | # Post a comment
37 | comment: |
38 | Thank you for submitting this issue. We would love to assist you and diagnose this issue. However, since your website sits behind a proxy and/or requires authentication, I will not be able to access it. Our policy prevents us from accessing a customer's internal resources without prior written approval from the entity that owns the server/web resource.
39 |
40 | If you want us to research the problem further, we'll need access to the server/web resource. Please ask the website owner to send us ([support@devexpress.com](mailto:support@devexpress.com)) written confirmation. It must permit DevExpress personnel to remotely access the website and its internal resources for research/testing and debugging purposes.
41 | label: 'STATE: Need clarification'
42 | unlabel:
43 | - 'STATE: Need access confirmation'
44 | - 'STATE: Need response'
45 |
46 | ? 'STATE: Incomplete template'
47 | :
48 | # Post a comment
49 | comment: |
50 | Thank you for submitting this issue. However, the information you shared is insufficient to determine its cause. Please submit a new issue and fill the issue template completely - specify your TestCafe version and share a sample application with a test case that we can run on our side to reproduce and research the issue.
51 | unlabel:
52 | - 'STATE: Incomplete template'
53 | - 'STATE: Need response'
54 | close: true
55 |
56 | ? 'STATE: No updates'
57 | :
58 | # Post a comment
59 | comment: |
60 | No updates yet. Once we get any results, we will post them in this thread.
61 | unlabel:
62 | - 'STATE: No updates'
63 | - 'STATE: Need response'
64 |
65 | ? 'STATE: No estimations'
66 | :
67 | # Post a comment
68 | comment: |
69 | Any personal estimate may be misleading, so we cannot currently tell it at the moment. Once we get any results, we will post them in this thread.
70 | unlabel:
71 | - 'STATE: No estimations'
72 | - 'STATE: Need response'
73 |
74 | ? 'STATE: Outdated proposal'
75 | :
76 | # Post a comment
77 | comment: |
78 | We are focused on implementing features from our [Roadmap](https://devexpress.github.io/testcafe/roadmap/). Since this enhancement is not there, we cannot tell you any time frames on when we will be able to address it. If this feature is important for you, please submit a Pull Request with its implementation. See the [Сontribution guide](https://github.com/DevExpress/testcafe/blob/master/CONTRIBUTING.md) for more information.
79 | unlabel:
80 | - 'STATE: Outdated proposal'
81 | - 'STATE: Need response'
82 |
83 | ? 'STATE: Outdated issue'
84 | :
85 | # Post a comment
86 | comment: |
87 | We address issues according to their severity, priority, and other factors. It appears that this issue is an edge case. If this issue is important for you, please submit a Pull Request with a fix. See the [Сontribution guide](https://github.com/DevExpress/testcafe/blob/master/CONTRIBUTING.md) for more information.
88 | unlabel:
89 | - 'STATE: Outdated issue'
90 | - 'STATE: Need response'
91 |
92 | ? 'STATE: No workarounds'
93 | :
94 | # Post a comment
95 | comment: |
96 | There are no workarounds. Once we get any updates, we will post them in this thread.
97 | unlabel:
98 | - 'STATE: No workarounds'
99 | - 'STATE: Need response'
100 |
101 | ? 'STATE: PR Review Pending'
102 | :
103 | # Post a comment
104 | comment: |
105 | Thank you for your contribution to TestCafe. We will review this PR.
106 | unlabel:
107 | - 'STATE: PR Review Pending'
108 | - 'STATE: Need response'
109 |
110 | ? 'STATE: Issue accepted'
111 | :
112 | # Post a comment
113 | comment: |
114 | We appreciate you taking the time to share the information about this issue. We replicated it, and it is currently in our internal queue. Please note that the research may take time. We'll update this thread once we have news.
115 | unlabel:
116 | - 'STATE: Issue accepted'
117 | - 'STATE: Need response'
118 |
119 | ? 'STATE: Enhancement accepted'
120 | :
121 | # Post a comment
122 | comment: |
123 | Thank you for bringing this to our attention. We will be happy to look into this enhancement. We'll update this thread once we have news to share. In the absence of communication from us, it's safe to assume that there are no updates.
124 | unlabel:
125 | - 'STATE: Enhancement accepted'
126 | - 'STATE: Need response'
--------------------------------------------------------------------------------
/.github/workflows/check-security-alerts.yml:
--------------------------------------------------------------------------------
1 | name: Check security alerts
2 |
3 | on:
4 | schedule:
5 | - cron: "30 1 * * *"
6 | workflow_dispatch:
7 |
8 | jobs:
9 | check:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/github-script@v6
13 | with:
14 | github-token: ${{ secrets.ACTIVE_TOKEN }}
15 | script: |
16 | if (!'${{secrets.SECURITY_ISSUE_REPO}}')
17 | return;
18 |
19 | const { owner, repo } = context.repo;
20 | const state = 'open';
21 | const dependabotLabel = 'dependabot';
22 | const codeqlLabel = 'codeql';
23 | const securityLabel = 'security';
24 |
25 | async function getDependabotAlerts () {
26 | const dependabotListAlertsUrl = `https://api.github.com/repos/${ owner }/${ repo }/dependabot/alerts?state=${ state }`;
27 | const dependabotRequestOptions = {
28 | headers: { 'Authorization': 'Bearer ${{ secrets.ACTIVE_TOKEN }}' }
29 | }
30 |
31 | const response = await fetch(dependabotListAlertsUrl, dependabotRequestOptions);
32 | const data = await response.json();
33 |
34 | // If data isn't arry somethig goes wrong
35 | if (Array.isArray(data))
36 | return data;
37 |
38 | return [];
39 | }
40 |
41 | async function getCodeqlAlerts () {
42 | // When CodeQL is turned of it throws error
43 | try {
44 | const { data } = await github.rest.codeScanning.listAlertsForRepo({ owner, repo, state });
45 |
46 | return data;
47 | } catch (_) {
48 | return [];
49 | }
50 | }
51 |
52 | async function createIssue ({owner, repo, labels, summary, description, link, package = ''}) {
53 | const title = `[${repo}] ${summary}`;
54 | const body = ''
55 | + `#### Repository: \`${ repo }\`\n`
56 | + (!!package ? `#### Package: \`${ package }\`\n` : '')
57 | + `#### Description:\n`
58 | + `${ description }\n`
59 | + `#### Link: ${ link }`
60 |
61 | return github.rest.issues.create({ owner, repo, title, body, labels });
62 | }
63 |
64 | function needCreateIssue (alert) {
65 | return !issueDictionary[alert.html_url]
66 | && Date.now() - new Date(alert.updated_at) <= 1000 * 60 * 60 * 24;
67 | }
68 |
69 | const dependabotAlerts = await getDependabotAlerts();
70 | const codeqlAlerts = await getCodeqlAlerts();
71 | const {data: existedIssues} = await github.rest.issues.listForRepo({ owner, repo, labels: [securityLabel], state });
72 |
73 | const issueDictionary = existedIssues.reduce((res, issue) => {
74 | const alertUrl = issue.body.match(/Link:\s*(https.*\d*)/)?.[1];
75 |
76 | if (alertUrl)
77 | res[alertUrl] = issue;
78 |
79 | return res;
80 | }, {})
81 |
82 | dependabotAlerts.forEach(alert => {
83 | if (!needCreateIssue(alert))
84 | return;
85 |
86 | createIssue({ owner,
87 | repo: '${{ secrets.SECURITY_ISSUE_REPO }}',
88 | labels: [dependabotLabel, securityLabel],
89 | summary: alert.security_advisory.summary,
90 | description: alert.security_advisory.description,
91 | link: alert.html_url,
92 | package: alert.dependency.package.name
93 | })
94 | });
95 |
96 | codeqlAlerts.forEach(alert => {
97 | if (!needCreateIssue(alert))
98 | return;
99 |
100 | createIssue({ owner,
101 | repo: '${{ secrets.SECURITY_ISSUE_REPO }}',
102 | labels: [codeqlLabel, securityLabel],
103 | summary: alert.rule.description,
104 | description: alert.most_recent_instance.message.text,
105 | link: alert.html_url,
106 | })
107 | });
--------------------------------------------------------------------------------
/.github/workflows/handle-labels.yml:
--------------------------------------------------------------------------------
1 | name: 'Label Actions'
2 |
3 | on:
4 | issues:
5 | types: [labeled, unlabeled]
6 | pull_request_target:
7 | types: [labeled, unlabeled]
8 |
9 | jobs:
10 | reaction:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: DevExpress/testcafe-build-system/actions/handle-labels@main
14 |
--------------------------------------------------------------------------------
/.github/workflows/handle-stale.yml:
--------------------------------------------------------------------------------
1 | name: "Mark stale issues and pull requests"
2 | on:
3 | schedule:
4 | - cron: "30 1 * * *"
5 | workflow_dispatch:
6 | jobs:
7 | stale:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/stale@v3
11 | with:
12 | stale-issue-message: "This issue has been automatically marked as stale because it has not had any activity for a long period. It will be closed and archived if no further activity occurs. However, we may return to this issue in the future. If it still affects you or you have any additional information regarding it, please leave a comment and we will keep it open."
13 | stale-pr-message: "This pull request has been automatically marked as stale because it has not had any activity for a long period. It will be closed and archived if no further activity occurs. However, we may return to this pull request in the future. If it is still relevant or you have any additional information regarding it, please leave a comment and we will keep it open."
14 | close-issue-message: "We're closing this issue after a prolonged period of inactivity. If it still affects you, please add a comment to this issue with up-to-date information. Thank you."
15 | close-pr-message: "We're closing this pull request after a prolonged period of inactivity. If it is still relevant, please ask for this pull request to be reopened. Thank you."
16 | stale-issue-label: "STATE: Stale"
17 | stale-pr-label: "STATE: Stale"
18 | days-before-stale: 365
19 | days-before-close: 10
20 | exempt-issue-labels: "AREA: docs,FREQUENCY: critical,FREQUENCY: level 2,HELP WANTED,!IMPORTANT!,STATE: Need clarification,STATE: Need response,STATE: won't fix,support center"
21 | exempt-pr-labels: "AREA: docs,FREQUENCY: critical,FREQUENCY: level 2,HELP WANTED,!IMPORTANT!,STATE: Need clarification,STATE: Need response,STATE: won't fix,support center"
--------------------------------------------------------------------------------
/.github/workflows/no-response.yml:
--------------------------------------------------------------------------------
1 | name: No Response
2 |
3 | # Both `issue_comment` and `scheduled` event types are required for this Action
4 | # to work properly.
5 | on:
6 | issue_comment:
7 | types: [created]
8 | schedule:
9 | # Schedule for five minutes after the hour, every hour
10 | - cron: '5 * * * *'
11 |
12 | jobs:
13 | noResponse:
14 | runs-on: ubuntu-latest
15 | steps:
16 | - uses: lee-dohm/no-response@v0.5.0
17 | with:
18 | token: ${{ github.token }}
19 | daysUntilClose: 10
20 | responseRequiredLabel: "STATE: Need clarification"
21 | closeComment: >
22 | This issue was automatically closed because there was no response
23 | to our request for more information from the original author.
24 | Currently, we don't have enough information to take action.
25 | Please reach out to us if you find the necessary information
26 | and are able to share it. We are also eager to know if you resolved
27 | the issue on your own and can share your findings with everyone.
28 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | package-lock.json
2 | # Windows image file caches
3 | Thumbs.db
4 | ehthumbs.db
5 |
6 | # Folder config file
7 | Desktop.ini
8 |
9 | # Recycle Bin used on file shares
10 | $RECYCLE.BIN/
11 |
12 | # Windows Installer files
13 | *.cab
14 | *.msi
15 | *.msm
16 | *.msp
17 |
18 | # Windows shortcuts
19 | *.lnk
20 |
21 | # =========================
22 | # Operating System Files
23 | # =========================
24 |
25 | # OSX
26 | # =========================
27 |
28 | .DS_Store
29 | .AppleDouble
30 | .LSOverride
31 |
32 | # Thumbnails
33 | ._*
34 |
35 | # Files that might appear in the root of a volume
36 | .DocumentRevisions-V100
37 | .fseventsd
38 | .Spotlight-V100
39 | .TemporaryItems
40 | .Trashes
41 | .VolumeIcon.icns
42 |
43 | # Directories potentially created on remote AFP share
44 | .AppleDB
45 | .AppleDesktop
46 | Network Trash Folder
47 | Temporary Items
48 | .apdisk
49 |
50 | node_modules/*
51 | .idea/*
52 | test/data/lib/*
53 | package-lock.json
54 |
--------------------------------------------------------------------------------
/.publishrc:
--------------------------------------------------------------------------------
1 | {
2 | "validations": {
3 | "vulnerableDependencies": false,
4 | "uncommittedChanges": true,
5 | "untrackedFiles": true,
6 | "sensitiveData": true,
7 | "branch": "master",
8 | "gitTag": true
9 | },
10 | "confirm": true,
11 | "publishTag": "latest",
12 | "prePublishScript": "npm test"
13 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License
2 |
3 | Copyright (C) 2017-2021 Developer Express Inc.
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
13 | all 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
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # DEPREDCATED
2 | The TestCafe team no longer maintains the `testcafe-vue-selectors` repository. If you want to take over the project, we'll be happy to hand it over. To contact the team, create a new GitHub issue.
3 |
4 | ## testcafe-vue-selectors
5 |
6 | This plugin provides selector extensions that make it easier to test Vue components with [TestCafe](https://github.com/DevExpress/testcafe).
7 | These extensions allow you to test Vue component state and result markup together.
8 |
9 | ### Install
10 |
11 | `$ npm install testcafe-vue-selectors`
12 |
13 | ### Usage
14 |
15 | ##### Create selectors for Vue components
16 |
17 | `VueSelector` allows you to select page elements by the component tagName or the nested component tagNames.
18 |
19 | Suppose you have the following markup.
20 |
21 | ```html
22 |
23 |
24 |
25 | Item 1
26 | Item 2
27 |
28 |
Items count: {{itemCount}}
29 |
30 |
40 | ```
41 |
42 | To get the root Vue node, use the `VueSelector` constructor without parameters.
43 |
44 | ```js
45 | import VueSelector from 'testcafe-vue-selectors';
46 |
47 | const rootVue = VueSelector();
48 | ```
49 |
50 | The `rootVue` variable will contain the `` element.
51 |
52 |
53 | To get a root DOM element for a component, pass the component name to the `VueSelector` constructor.
54 |
55 | ```js
56 | import VueSelector from 'testcafe-vue-selectors';
57 |
58 | const todoInput = VueSelector('todo-input');
59 | ```
60 |
61 | To obtain a component based on its [reference ID](https://vuejs.org/v2/guide/components-edge-cases.html#Accessing-Child-Component-Instances-amp-Child-Elements), pass the [ref](https://vuejs.org/v2/api/#ref) attribute value prepended with `ref:` to the `VueSelector` constructor.
62 |
63 | ```js
64 | import VueSelector from 'testcafe-vue-selectors';
65 |
66 | const todoInput = VueSelector('ref:ref-todo-item-1');
67 | ```
68 |
69 | To obtain a nested component, you can use a combined selector.
70 |
71 | ```js
72 | import VueSelector from 'testcafe-vue-selectors';
73 |
74 | const todoItem = VueSelector('todo-list todo-item');
75 | const todoList1Items = VueSelector('ref:todo-list-1 todo-item');
76 | const todoItem1 = VueSelector('todo-list ref:ref-todo-item-1');
77 | const todoList1Item1 = VueSelector('ref:ref-todo-list-1 ref:ref-todo-item-1');
78 | ```
79 |
80 | You can combine Vue selectors with testcafe `Selector` filter functions like `.find`, `.withText`, `.nth` and [other](http://devexpress.github.io/testcafe/documentation/test-api/selecting-page-elements/selectors.html#functional-style-selectors).
81 |
82 | ```js
83 | import VueSelector from 'testcafe-vue-selectors';
84 |
85 | var itemsCount = VueSelector().find('.items-count span');
86 | ```
87 |
88 | Let’s use the API described above to add a task to a Todo list and check that the number of items changed.
89 |
90 | ```js
91 | import VueSelector from 'testcafe-vue-selectors';
92 |
93 | fixture `TODO list test`
94 | .page('http://localhost:1337');
95 |
96 | test('Add new task', async t => {
97 | const todoTextInput = VueSelector('todo-input');
98 | const todoItem = VueSelector('todo-list todo-item');
99 |
100 | await t
101 | .typeText(todoTextInput, 'My Item')
102 | .pressKey('enter')
103 | .expect(todoItem.count).eql(3);
104 | });
105 | ```
106 |
107 | ##### Obtaining component's props, computed, state and ref
108 |
109 | In addition to [DOM Node State](http://devexpress.github.io/testcafe/documentation/test-api/selecting-page-elements/dom-node-state.html), you can obtain `state`, `computed`, `props` or `ref` of a Vue component.
110 |
111 | To get these data, use the Vue selector’s `.getVue()` method.
112 |
113 | The `getVue()` method returns a [client function](https://devexpress.github.io/testcafe/documentation/test-api/obtaining-data-from-the-client.html). This function resolves to an object that contains component properties.
114 |
115 | ```js
116 | const vueComponent = VueSelector('componentTag');
117 | const vueComponentState = await vueComponent.getVue();
118 |
119 | // >> vueComponentState
120 | //
121 | // {
122 | // props:
,
123 | // state: ,
124 | // computed: ,
125 | // ref:
126 | // }
127 | ```
128 |
129 | The returned client function can be passed to assertions activating the [Smart Assertion Query mechanism](https://devexpress.github.io/testcafe/documentation/test-api/assertions/#smart-assertion-query-mechanism).
130 |
131 | Example
132 | ```js
133 | import VueSelector from 'testcafe-vue-selector';
134 |
135 | fixture `TODO list test`
136 | .page('http://localhost:1337');
137 |
138 | test('Check list item', async t => {
139 | const todoItem = VueSelector('todo-item');
140 | const todoItemVue = await todoItem.getVue();
141 |
142 | await t
143 | .expect(todoItemVue.props.priority).eql('High')
144 | .expect(todoItemVue.state.isActive).eql(false)
145 | .expect(todoItemVue.computed.text).eql('Item 1');
146 | });
147 | ```
148 |
149 | As an alternative, the `.getVue()` method can take a function that returns the required property, state, computed property or reference ID. This function acts as a filter. Its argument is an object returned by `.getVue()`, i.e. `{ props: ..., state: ..., computed: ..., ref: ...}`.
150 |
151 | ```js
152 | VueSelector('component').getVue(({ props, state, computed, ref }) => {...});
153 | ```
154 |
155 |
156 | Example
157 | ```js
158 | import VueSelector from 'testcafe-vue-selectors';
159 |
160 | fixture `TODO list test`
161 | .page('http://localhost:1337');
162 |
163 | test('Check list item', async t => {
164 | const todoItem = VueSelector('todo-item');
165 |
166 | await t
167 | .expect(todoItem.getVue(({ props }) => props.priority)).eql('High')
168 | .expect(todoItem.getVue(({ state }) => state.isActive)).eql(false)
169 | .expect(todoItem.getVue(({ computed }) => computed.text)).eql('Item 1')
170 | .expect(todoItem.getVue(({ ref }) => ref)).eql('ref-item-1');
171 | });
172 |
173 | ```
174 |
175 | The `.getVue()` method can be called for the `VueSelector` or the snapshot this selector returns.
176 |
177 | ##### Limitations
178 |
179 | `testcafe-vue-selectors` support Vue starting with version 2.
180 |
181 | Only the property, state, computed property and reference ID parts of a Vue component are available.
182 |
183 | To check if a component can be found, use the [vue-dev-tools](https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd) extension for Google Chrome.
184 |
185 | Pages with multiple Vue root nodes are not supported.
186 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | environment:
2 | NODEJS_VERSION: "stable"
3 |
4 | install:
5 | - ps: >-
6 | Install-Product node $env:NODEJS_VERSION
7 |
8 | choco install GoogleChrome
9 |
10 | choco install Firefox
11 |
12 | - cmd: >-
13 | npm install
14 |
15 | build: off
16 |
17 | test_script:
18 | - cmd: >-
19 | npm test
--------------------------------------------------------------------------------
/lib/index.js:
--------------------------------------------------------------------------------
1 | const { Selector } = require('testcafe');
2 |
3 | module.exports = Selector(complexSelector => {
4 | window['%findVueRoot%'] = (node = document) => {
5 | const treeWalker = document.createTreeWalker(
6 | node,
7 | NodeFilter.SHOW_ELEMENT,
8 | () => NodeFilter.FILTER_ACCEPT,
9 | false
10 | );
11 |
12 | let instance = node.__vue__;
13 |
14 | while (!instance && treeWalker.nextNode())
15 | instance = treeWalker.currentNode.__vue__;
16 |
17 | return instance;
18 | };
19 |
20 | window['%vueSelector%'] = (complexSelector_, vueRoots = null) => {
21 | function validateSelector (selector) {
22 | if (selector !== void 0 && typeof selector !== 'string')
23 | throw new Error('If the selector parameter is passed it should be a string, but it was ' + typeof selector);
24 | }
25 |
26 | function validateVueVersion (rootInstance) {
27 | const MAJOR_SUPPORTED_VUE_VERSION = 2;
28 | const vueVersion = parseInt(findVueConstructor(rootInstance).version.split('.')[0], 10);
29 |
30 | if (vueVersion < MAJOR_SUPPORTED_VUE_VERSION)
31 | throw new Error('testcafe-vue-selectors supports Vue version 2.x and newer');
32 | }
33 |
34 | function findVueConstructor (rootInstance) {
35 | let Vue = Object.getPrototypeOf(rootInstance).constructor;
36 |
37 | while (Vue.super)
38 | Vue = Vue.super;
39 |
40 | return Vue;
41 | }
42 |
43 | function getComponentTagNames (componentSelector) {
44 | return componentSelector
45 | .split(' ')
46 | .filter(el => !!el)
47 | .map(el => el.trim());
48 | }
49 |
50 | function getComponentTag (instance) {
51 | return instance.$options.name ||
52 | instance.$options._componentTag ||
53 | instance.$options.__file ||
54 | '';
55 | }
56 |
57 | function isRef (selector) {
58 | return selector.indexOf('ref:') !== -1;
59 | }
60 |
61 | function getRef (selector) {
62 | if (selector.indexOf('ref:') === 0 && selector.split('ref:')[1])
63 | return selector.split('ref:')[1];
64 |
65 | throw new Error('If the ref is passed as selector it should be in the format \'ref:ref-selector\'');
66 | }
67 |
68 | function getRefOfNode (node) {
69 | if (node.$vnode && node.$vnode.data)
70 | return node.$vnode.data.ref;
71 | return null;
72 | }
73 |
74 | function filterNodes (roots, tags) {
75 | const foundComponents = [];
76 |
77 | function walkVueComponentNodes (node, tagIndex, checkFn) {
78 | if (checkFn(node, tagIndex)) {
79 | if (tagIndex === tags.length - 1) {
80 | if (foundComponents.indexOf(node.$el) === -1)
81 | foundComponents.push(node.$el);
82 | return;
83 | }
84 |
85 | tagIndex++;
86 | }
87 |
88 | for (let i = 0; i < node.$children.length; i++) {
89 | const childNode = node.$children[i];
90 |
91 | walkVueComponentNodes(childNode, tagIndex, checkFn);
92 | }
93 | }
94 |
95 | roots.forEach(root => {
96 | walkVueComponentNodes(root, 0, (node, tagIndex) => {
97 | if (isRef(tags[tagIndex])) {
98 | const ref = getRef(tags[tagIndex]);
99 |
100 | return ref === getRefOfNode(node);
101 | }
102 | return tags[tagIndex] === getComponentTag(node);
103 | });
104 | });
105 |
106 | return foundComponents;
107 | }
108 |
109 |
110 | validateSelector(complexSelector_);
111 |
112 | const rootInstances = vueRoots ? vueRoots : [];
113 |
114 | if (!vueRoots) {
115 | const documentRoot = window['%findVueRoot%']();
116 |
117 | if (documentRoot)
118 | rootInstances.push(documentRoot);
119 |
120 | if (!documentRoot)
121 | return null;
122 | }
123 |
124 | rootInstances.map(validateVueVersion);
125 |
126 | if (!complexSelector_)
127 | return rootInstances.map(node => node.$el);
128 |
129 | const componentTags = getComponentTagNames(complexSelector_);
130 |
131 | return filterNodes(rootInstances, componentTags);
132 | };
133 |
134 | return window['%vueSelector%'](complexSelector);
135 | }).addCustomMethods({
136 | getVue: (node, fn) => {
137 | function getData (instance, prop) {
138 | const result = {};
139 |
140 | Object.keys(prop).forEach(key => {
141 | result[key] = instance[key];
142 | });
143 |
144 |
145 | return result;
146 | }
147 |
148 | function getProps (instance) {
149 | return getData(instance, instance.$options.props || {});
150 | }
151 |
152 | function getState (instance) {
153 | const props = instance._props || instance.$options.props;
154 | const getters = instance.$options.vuex && instance.$options.vuex.getters;
155 | const result = {};
156 |
157 | Object.keys(instance._data)
158 | .filter(key => !(props && key in props) && !(getters && key in getters))
159 | .forEach(key => {
160 | result[key] = instance._data[key];
161 | });
162 |
163 | return result;
164 | }
165 |
166 | function getComputed (instance) {
167 | return getData(instance, instance.$options.computed || {});
168 | }
169 |
170 | function getComponentReference (instance) {
171 | return instance.$vnode && instance.$vnode.data && instance.$vnode.data.ref;
172 | }
173 |
174 | const nodeVue = node.__vue__;
175 |
176 | if (!nodeVue)
177 | return null;
178 |
179 | const props = getProps(nodeVue);
180 | const state = getState(nodeVue);
181 | const computed = getComputed(nodeVue);
182 | const ref = getComponentReference(nodeVue);
183 |
184 | if (typeof fn === 'function')
185 | return fn({ props, state, computed, ref });
186 |
187 | return { props, state, computed, ref };
188 | }
189 | }).addCustomMethods({
190 | findVue: (nodes, selector) => {
191 | return window['%vueSelector%'](selector, nodes.map(window['%findVueRoot%']));
192 | },
193 | }, { returnDOMNodes: true });
194 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "testcafe-vue-selectors",
3 | "version": "3.1.1",
4 | "description": "VueJS selectors for TestCafe",
5 | "repository": "https://github.com/DevExpress/testcafe-vue-selectors",
6 | "author": {
7 | "name": "Developer Express Inc.",
8 | "url": "https://devexpress.com"
9 | },
10 | "main": "lib/index",
11 | "files": [
12 | "lib"
13 | ],
14 | "license": "MIT",
15 | "scripts": {
16 | "lint": "eslint lib/index.js",
17 | "test-server": "webpack-dev-server",
18 | "testcafe": "testcafe all test/*.js --app \"npm run test-server\" --app-init-delay 10000",
19 | "test": "npm run lint && npm run testcafe",
20 | "publish-please": "publish-please",
21 | "prepublish": "publish-please guard"
22 | },
23 | "devDependencies": {
24 | "babel-loader": "^8.2.2",
25 | "css-loader": "^5.1.1",
26 | "eslint": "^7.21.0",
27 | "eslint-plugin-testcafe": "^0.2.1",
28 | "prettier": "1.12.1",
29 | "publish-please": "^5.4.3",
30 | "pug": "^3.0.2",
31 | "testcafe": "*",
32 | "vue": "^2.5.4",
33 | "vue-loader": "^14.2.2",
34 | "vue-router": "^3.0.1",
35 | "vue-template-compiler": "^2.5.4",
36 | "webpack": "^4.0.0",
37 | "webpack-cli": "^3.2.1",
38 | "webpack-dev-server": "^3.1.11"
39 | },
40 | "keywords": [
41 | "testcafe",
42 | "vue",
43 | "vuejs",
44 | "selectors",
45 | "plugin"
46 | ],
47 | "peerDependencies": {
48 | "testcafe": "*"
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/test/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | "no-unused-expressions": 0,
4 | "no-return-assign": 0
5 | },
6 | "plugins": [
7 | "testcafe"
8 | ],
9 | "extends": "plugin:testcafe/recommended"
10 | }
--------------------------------------------------------------------------------
/test/data/vue-js/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Title
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/test/data/vue-loader/components/app.vue:
--------------------------------------------------------------------------------
1 |
2 | #app
3 | h1 Header
4 | list#list1
5 | list#list2
6 |
7 |
15 |
17 |
--------------------------------------------------------------------------------
/test/data/vue-loader/components/list-item.vue:
--------------------------------------------------------------------------------
1 |
2 | li(:id='id')
3 | p {{ id }}
4 |
5 |
11 |
16 |
--------------------------------------------------------------------------------
/test/data/vue-loader/components/list.vue:
--------------------------------------------------------------------------------
1 |
2 | .list-wrapper
3 | h4 {{ id }}
4 | ul(:id='id')
5 | each index in [1, 2, 3]
6 | list-item(:id=`getId(${index})`)
7 |
8 |
27 |
38 |
--------------------------------------------------------------------------------
/test/data/vue-loader/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | vue-loader
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/test/data/vue-loader/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import App from './components/app.vue';
3 |
4 | new Vue({
5 | el: '#app',
6 | render: h => h(App),
7 | data () {
8 | return {
9 | rootProp1: 1
10 | };
11 | }
12 | });
13 |
--------------------------------------------------------------------------------
/test/data/vue-router/components/bar.vue:
--------------------------------------------------------------------------------
1 |
2 | #bar
3 | h1 Header Bar
4 | list#list-bar-1
5 | list#list-bar-2
6 | list#list-bar-3
7 |
8 |
16 |
18 |
--------------------------------------------------------------------------------
/test/data/vue-router/components/foo.vue:
--------------------------------------------------------------------------------
1 |
2 | #foo
3 | h1 Header Foo
4 | list#list-foo-1
5 |
6 |
14 |
16 |
--------------------------------------------------------------------------------
/test/data/vue-router/components/list-item.vue:
--------------------------------------------------------------------------------
1 |
2 | li(:id='id')
3 | p {{ id }}
4 |
5 |
11 |
16 |
--------------------------------------------------------------------------------
/test/data/vue-router/components/list.vue:
--------------------------------------------------------------------------------
1 |
2 | .list-wrapper
3 | h4 {{ id }}
4 | ul(:id='id')
5 | each index in [1, 2, 3]
6 | list-item(:id=`getId(${index})`)
7 |
8 |
27 |
38 |
--------------------------------------------------------------------------------
/test/data/vue-router/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | vue-router
6 |
7 |
8 |
9 |
vue-router
10 |
11 |
12 |
13 |
14 | Go to Foo
15 | Go to Bar
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/test/data/vue-router/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import VueRouter from 'vue-router';
3 |
4 | Vue.use(VueRouter);
5 |
6 | import Foo from './components/foo.vue';
7 | import Bar from './components/bar.vue';
8 |
9 | const routes = [
10 | { path: '/foo', component: Foo },
11 | { path: '/bar', component: Bar }
12 | ];
13 |
14 | const router = new VueRouter({
15 | routes
16 | });
17 |
18 | new Vue({
19 | el: '#app',
20 | router,
21 | data () {
22 | return {
23 | rootProp1: 1
24 | };
25 | }
26 | });
27 |
--------------------------------------------------------------------------------
/test/vue-js.js:
--------------------------------------------------------------------------------
1 | import VueSelector from '../lib';
2 | import { ClientFunction } from 'testcafe';
3 |
4 | fixture `vue-js`
5 | .page `http://localhost:8080/test/data/vue-js/`;
6 |
7 | test('root node', async t => {
8 | const root = VueSelector();
9 | const rootVue = await root.getVue();
10 |
11 | await t
12 | .expect(root.exists).ok()
13 | .expect(rootVue.state.rootProp1).eql(1);
14 | });
15 |
16 | test('selector', async t => {
17 | const list = VueSelector('list');
18 | const listVue = await list.getVue();
19 |
20 | await t
21 | .expect(list.count).eql(2)
22 | .expect(VueSelector('list-item').count).eql(7)
23 | .expect(listVue.props.id).eql('list1')
24 | .expect(listVue.computed.reversedId).eql('1tsil');
25 | });
26 |
27 | test('composite selector', async t => {
28 | const listItem = VueSelector('list list-item');
29 | const listItemVue6 = await listItem.nth(5).getVue();
30 | const listItemVue5Id = listItem.nth(4).getVue(({ props }) => props.id);
31 |
32 | await t
33 | .expect(listItem.count).eql(6)
34 | .expect(listItemVue6.props.id).eql('list2-item3')
35 | .expect(listItemVue5Id).eql('list2-item2');
36 | });
37 |
38 | test('findVue chain selector', async t => {
39 | const allListItem = VueSelector().findVue('list-item');
40 |
41 | await t
42 | .expect(allListItem.count).eql(7)
43 | .expect((await allListItem.nth(6).getVue()).props.id).eql('extra');
44 |
45 | const listItem = VueSelector('list').findVue('list-item');
46 | const listItemVue6 = await listItem.nth(5).getVue();
47 | const listItemVue5Id = listItem.nth(4).getVue(({ props }) => props.id);
48 |
49 | await t
50 | .expect(listItem.count).eql(6)
51 | .expect(listItemVue6.props.id).eql('list2-item3')
52 | .expect(listItemVue5Id).eql('list2-item2');
53 | });
54 |
55 | test('VueSelector(\'ref:list-n\')', async t => {
56 | const list1 = VueSelector('ref:list-1');
57 | const list1Vue = await list1.getVue();
58 | const list2 = VueSelector('ref:list-2');
59 | const list2Vue = await list2.getVue();
60 |
61 | await t
62 | .expect(list1.count).eql(1)
63 | .expect(list1Vue.ref).eql('list-1')
64 | .expect(list1Vue.props.id).eql('list1')
65 | .expect(list1Vue.computed.reversedId).eql('1tsil')
66 | .expect(list2.count).eql(1)
67 | .expect(list2Vue.ref).eql('list-2')
68 | .expect(list2Vue.props.id).eql('list2')
69 | .expect(list2Vue.computed.reversedId).eql('2tsil');
70 | });
71 |
72 | test('VueSelector(\'list ref:list-item-n\')', async t => {
73 | const listItems1 = VueSelector('list ref:list-item-1');
74 | const list1Item1Vue = await listItems1.nth(0).getVue();
75 | const list2Item1Vue = await listItems1.nth(1).getVue();
76 | const listItems2 = VueSelector('list ref:list-item-2');
77 | const list1Item2Vue = await listItems2.nth(0).getVue();
78 | const list2Item2Vue = await listItems2.nth(1).getVue();
79 | const listItems3 = VueSelector('list ref:list-item-3');
80 | const list1Item3Vue = await listItems3.nth(0).getVue();
81 | const list2Item3Vue = await listItems3.nth(1).getVue();
82 |
83 | await t
84 | .expect(listItems1.count).eql(2)
85 | .expect(list1Item1Vue.ref).eql('list-item-1')
86 | .expect(list1Item1Vue.props.id).eql('list1-item1')
87 | .expect(list2Item1Vue.ref).eql('list-item-1')
88 | .expect(list2Item1Vue.props.id).eql('list2-item1')
89 | .expect(listItems2.count).eql(2)
90 | .expect(list1Item2Vue.ref).eql('list-item-2')
91 | .expect(list1Item2Vue.props.id).eql('list1-item2')
92 | .expect(list2Item2Vue.ref).eql('list-item-2')
93 | .expect(list2Item2Vue.props.id).eql('list2-item2')
94 | .expect(listItems3.count).eql(2)
95 | .expect(list1Item3Vue.ref).eql('list-item-3')
96 | .expect(list1Item3Vue.props.id).eql('list1-item3')
97 | .expect(list2Item3Vue.ref).eql('list-item-3')
98 | .expect(list2Item3Vue.props.id).eql('list2-item3');
99 | });
100 |
101 | test('VueSelector(\'ref:list-n list-item\')', async t => {
102 | const list1Items = VueSelector('ref:list-1 list-item');
103 | const list1Item1Vue = await list1Items.nth(0).getVue();
104 | const list1Item2Vue = await list1Items.nth(1).getVue();
105 | const list1Item3Vue = await list1Items.nth(2).getVue();
106 | const list2Items = VueSelector('ref:list-2 list-item');
107 | const list2Item1Vue = await list2Items.nth(0).getVue();
108 | const list2Item2Vue = await list2Items.nth(1).getVue();
109 | const list2Item3Vue = await list2Items.nth(2).getVue();
110 |
111 | await t
112 | .expect(list1Items.count).eql(3)
113 | .expect(list1Item1Vue.ref).eql('list-item-1')
114 | .expect(list1Item1Vue.props.id).eql('list1-item1')
115 | .expect(list1Item2Vue.ref).eql('list-item-2')
116 | .expect(list1Item2Vue.props.id).eql('list1-item2')
117 | .expect(list1Item3Vue.ref).eql('list-item-3')
118 | .expect(list1Item3Vue.props.id).eql('list1-item3')
119 | .expect(list2Items.count).eql(3)
120 | .expect(list2Item1Vue.ref).eql('list-item-1')
121 | .expect(list2Item1Vue.props.id).eql('list2-item1')
122 | .expect(list2Item2Vue.ref).eql('list-item-2')
123 | .expect(list2Item2Vue.props.id).eql('list2-item2')
124 | .expect(list2Item3Vue.ref).eql('list-item-3')
125 | .expect(list2Item3Vue.props.id).eql('list2-item3');
126 | });
127 |
128 | test('VueSelector(\'ref:list-n ref:list-item-n\')', async t => {
129 | const list1Item1 = VueSelector('ref:list-1 ref:list-item-1');
130 | const list1Item2 = VueSelector('ref:list-1 ref:list-item-2');
131 | const list1Item3 = VueSelector('ref:list-1 ref:list-item-3');
132 | const list2Item1 = VueSelector('ref:list-2 ref:list-item-1');
133 | const list2Item2 = VueSelector('ref:list-2 ref:list-item-2');
134 | const list2Item3 = VueSelector('ref:list-2 ref:list-item-3');
135 | const list1Item1Vue = await list1Item1.getVue();
136 | const list1Item2Vue = await list1Item2.getVue();
137 | const list1Item3Vue = await list1Item3.getVue();
138 | const list2Item1Vue = await list2Item1.getVue();
139 | const list2Item2Vue = await list2Item2.getVue();
140 | const list2Item3Vue = await list2Item3.getVue();
141 |
142 |
143 | await t
144 | .expect(list1Item1.count).eql(1)
145 | .expect(list1Item1Vue.ref).eql('list-item-1')
146 | .expect(list1Item1Vue.props.id).eql('list1-item1')
147 | .expect(list1Item2.count).eql(1)
148 | .expect(list1Item2Vue.ref).eql('list-item-2')
149 | .expect(list1Item2Vue.props.id).eql('list1-item2')
150 | .expect(list1Item3.count).eql(1)
151 | .expect(list1Item3Vue.ref).eql('list-item-3')
152 | .expect(list1Item3Vue.props.id).eql('list1-item3')
153 | .expect(list2Item1.count).eql(1)
154 | .expect(list2Item1Vue.ref).eql('list-item-1')
155 | .expect(list2Item1Vue.props.id).eql('list2-item1')
156 | .expect(list2Item2.count).eql(1)
157 | .expect(list2Item2Vue.ref).eql('list-item-2')
158 | .expect(list2Item2Vue.props.id).eql('list2-item2')
159 | .expect(list2Item3.count).eql(1)
160 | .expect(list2Item3Vue.ref).eql('list-item-3')
161 | .expect(list2Item3Vue.props.id).eql('list2-item3');
162 | });
163 |
164 | test('should throw exception for non-valid ref-selectors', async t => {
165 | for (const selector of ['ref:', 'list ref:', 'listref:list-1']) {
166 | try {
167 | await VueSelector(selector);
168 | await t.expect(false).ok('The selector should throw an error but it doesn\'t.');
169 | }
170 | catch (e) {
171 | await t
172 | .expect(e.errMsg).contains('If the ref is passed as selector it should be in the format \'ref:ref-selector\'');
173 | }
174 | }
175 | });
176 |
177 | test('should throw exception for non-valid selectors', async t => {
178 | for (const selector of [null, false, {}, 42]) {
179 | try {
180 | await VueSelector(selector);
181 | await t.expect(false).ok('The selector should throw an error but it doesn\'t.');
182 | }
183 | catch (e) {
184 | await t.expect(e.errMsg).contains(`If the selector parameter is passed it should be a string, but it was ${typeof selector}`);
185 | }
186 | }
187 | });
188 |
189 | test('supported version', async t => {
190 | await ClientFunction(() => window.Vue.version = '1.0.28')();
191 |
192 | try {
193 | await VueSelector();
194 | }
195 | catch (e) {
196 | await t.expect(e.errMsg).contains('testcafe-vue-selectors supports Vue version 2.x and newer');
197 | }
198 | });
199 |
--------------------------------------------------------------------------------
/test/vue-loader.js:
--------------------------------------------------------------------------------
1 | import VueSelector from '../lib';
2 |
3 | fixture `vue-loader`
4 | .page `http://localhost:8080/test/data/vue-loader/`;
5 |
6 | test('composite selector', async t => {
7 | const listItem = VueSelector('List ListItem');
8 | const listItemVue6 = await listItem.nth(5).getVue();
9 | const listItemVue5Id = listItem.nth(4).getVue(({ props }) => props.id);
10 |
11 | await t
12 | .expect(listItem.count).eql(6)
13 | .expect(listItemVue6.props.id).eql('list2-item3')
14 | .expect(listItemVue5Id).eql('list2-item2');
15 | });
16 |
--------------------------------------------------------------------------------
/test/vue-router.js:
--------------------------------------------------------------------------------
1 | import VueSelector from '../lib';
2 |
3 | fixture `vue-router`
4 | .page `http://localhost:8080/test/data/vue-router/`;
5 |
6 |
7 | const routerLinks = VueSelector('RouterLink');
8 |
9 | test('selector', async t => {
10 | await t
11 | .expect(routerLinks.count).eql(2)
12 | .click(routerLinks.nth(0))
13 | .expect(VueSelector('foo').exists).ok()
14 | .expect(VueSelector('bar').exists).notOk()
15 | .click(routerLinks.nth(1))
16 | .expect(VueSelector('foo').exists).notOk()
17 | .expect(VueSelector('bar').exists).ok();
18 | });
19 |
20 |
21 | test('component properties', async t => {
22 | await t
23 | .click(routerLinks.nth(0))
24 | .expect(VueSelector('foo').exists).ok();
25 |
26 | const listItem = VueSelector('list-item');
27 | const listItemVue2 = await listItem.nth(1).getVue();
28 | const listItemVue3Id = listItem.nth(2).getVue(({ props }) => props.id);
29 |
30 | await t
31 | .expect(listItem.count).eql(3)
32 | .expect(listItemVue2.props.id).eql('list-foo-1-item2')
33 | .expect(listItemVue3Id).eql('list-foo-1-item3');
34 | });
35 |
--------------------------------------------------------------------------------
/test/without-vue.js:
--------------------------------------------------------------------------------
1 | import VueSelector from '../lib';
2 |
3 | fixture `page without vue`;
4 |
5 | test('there is no Vue on the tested page', async t => {
6 | const listVueSelector = VueSelector('list');
7 |
8 | await t.expect(listVueSelector.count).eql(0);
9 | });
10 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 | mode: 'development',
5 |
6 | entry: {
7 | vueLoader: path.resolve(__dirname, './test/data/vue-loader/main.js'),
8 | vueRouter: path.resolve(__dirname, './test/data/vue-router/main.js')
9 | },
10 |
11 | output: {
12 | path: path.resolve(__dirname, 'dist'),
13 | filename: '[name].bundle.js'
14 | },
15 |
16 | module: {
17 | rules: [
18 | {
19 | test: /\.vue$/,
20 | loader: 'vue-loader'
21 | },
22 | {
23 | test: /\.js$/,
24 | loader: 'babel-loader',
25 | exclude: /node_modules/
26 | }
27 | ]
28 | },
29 |
30 | resolve: {
31 | alias: {
32 | 'vue$': 'vue/dist/vue.esm.js'
33 | }
34 | },
35 |
36 | devServer: {
37 | noInfo: true,
38 | port: 8080
39 | }
40 | };
41 |
--------------------------------------------------------------------------------