diff --git a/cypress/e2e/create-room/create-room.spec.ts b/cypress/e2e/create-room/create-room.spec.ts index a51c22ef862..9e923aec92f 100644 --- a/cypress/e2e/create-room/create-room.spec.ts +++ b/cypress/e2e/create-room/create-room.spec.ts @@ -20,8 +20,8 @@ import { HomeserverInstance } from "../../plugins/utils/homeserver"; import Chainable = Cypress.Chainable; function openCreateRoomDialog(): Chainable> { - cy.get('[aria-label="Add room"]').click(); - cy.get('.mx_ContextualMenu [aria-label="New room"]').click(); + cy.findButton("Add room").click(); + cy.findMenuitem("New room").click(); return cy.get(".mx_CreateRoomDialog"); } @@ -46,20 +46,23 @@ describe("Create Room", () => { openCreateRoomDialog().within(() => { // Fill name & topic - cy.get('[label="Name"]').type(name); - cy.get('[label="Topic (optional)"]').type(topic); + cy.findTextbox("Name").type(name); + cy.findTextbox("Topic (optional)").type(topic); // Change room to public - cy.get('[aria-label="Room visibility"]').click(); - cy.get("#mx_JoinRuleDropdown__public").click(); + cy.findButton("Room visibility").click(); + cy.findOption("Public room").click(); // Fill room address - cy.get('[label="Room address"]').type("test-room-1"); + cy.findTextbox("Room address").type("test-room-1"); // Submit - cy.get(".mx_Dialog_primary").click(); + cy.findButton("Create room").click(); }); cy.url().should("contain", "/#/room/#test-room-1:localhost"); - cy.contains(".mx_RoomHeader_nametext", name); - cy.contains(".mx_RoomHeader_topic", topic); + + cy.get(".mx_RoomHeader").within(() => { + cy.findByText(name); + cy.findByText(topic); + }); }); it("should create a room with a long room name, which is displayed with ellipsis", () => { diff --git a/cypress/support/e2e.ts b/cypress/support/e2e.ts index 10014a4bd64..bee59cd4c76 100644 --- a/cypress/support/e2e.ts +++ b/cypress/support/e2e.ts @@ -18,6 +18,7 @@ limitations under the License. import "@percy/cypress"; import "cypress-real-events"; +import "@testing-library/cypress/add-commands"; import "./homeserver"; import "./login"; @@ -37,3 +38,4 @@ import "./network"; import "./composer"; import "./proxy"; import "./axe"; +import "./find"; diff --git a/cypress/support/find.ts b/cypress/support/find.ts new file mode 100644 index 00000000000..98b3da6945a --- /dev/null +++ b/cypress/support/find.ts @@ -0,0 +1,70 @@ +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/// + +import Chainable = Cypress.Chainable; + +declare global { + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace Cypress { + interface Chainable { + /** + * Finds an element with the role "button". + * + * @param name - accessible name of the element to find + */ + findButton(name: string): Chainable; + /** + * Finds an element with the role "textbox". + * + * @param name - accessible name of the element to find + */ + findTextbox(name: string): Chainable; + /** + * Finds an element with the role "option". + * + * @param name - accessible name of the element to find + */ + findOption(name: string): Chainable; + /** + * Finds an element with the role "menuitem". + * + * @param name - accessible name of the element to find + */ + findMenuitem(name: string): Chainable; + } + } +} + +Cypress.Commands.add("findButton", (name: string): Chainable => { + return cy.findByRole("button", { name }); +}); + +Cypress.Commands.add("findTextbox", (name: string): Chainable => { + return cy.findByRole("textbox", { name }); +}); + +Cypress.Commands.add("findOption", (name: string): Chainable => { + return cy.findByRole("option", { name }); +}); + +Cypress.Commands.add("findMenuitem", (name: string): Chainable => { + return cy.findByRole("menuitem", { name }); +}); + +// Needed to make this file a module +export {}; diff --git a/cypress/tsconfig.json b/cypress/tsconfig.json index 3eb43ee9d17..bfff3775a4f 100644 --- a/cypress/tsconfig.json +++ b/cypress/tsconfig.json @@ -3,7 +3,7 @@ "target": "es2016", "jsx": "react", "lib": ["es2020", "dom", "dom.iterable"], - "types": ["cypress", "cypress-axe", "@percy/cypress"], + "types": ["cypress", "cypress-axe", "@percy/cypress", "@testing-library/cypress"], "resolveJsonModule": true, "esModuleInterop": true, "moduleResolution": "node", diff --git a/docs/cypress.md b/docs/cypress.md index 347139b5b34..8d95876c06b 100644 --- a/docs/cypress.md +++ b/docs/cypress.md @@ -154,6 +154,14 @@ API before logging the user in. You can make use of `cy.getBot(homeserver)` and We should probably end up with convenience APIs that wrap the homeserver creation, logging in and room creation that can be called to set up tests. +### Try to write tests from the users's perspective + +Like for instance a user will not look for a button by querying a CSS selector. Instead you should work +with roles / labels etc.. You can make use of `cy.findBy…` queries provided by +[Cypress Testing Library](https://github.com/testing-library/cypress-testing-library) and some convencience +commands, such as `findButton(name)` or `findTextbox(name)`. +See [`/cypress/support/find.ts`](../cypress/support/find.ts) for a complete list. + ### Using matrix-js-sdk Due to the way we run the Cypress tests in CI, at this time you can only use the matrix-js-sdk module diff --git a/package.json b/package.json index 15a5e65e12f..64b7c3c0fef 100644 --- a/package.json +++ b/package.json @@ -143,6 +143,7 @@ "@percy/cli": "^1.11.0", "@percy/cypress": "^3.1.2", "@sinonjs/fake-timers": "^9.1.2", + "@testing-library/cypress": "^9.0.0", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^12.1.5", "@testing-library/user-event": "^14.4.3", diff --git a/yarn.lock b/yarn.lock index ca1ba5485b3..d2d8ef9f8dd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1138,7 +1138,7 @@ pirates "^4.0.5" source-map-support "^0.5.16" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.17.9", "@babel/runtime@^7.20.7", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.6", "@babel/runtime@^7.15.4", "@babel/runtime@^7.17.9", "@babel/runtime@^7.20.7", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": version "7.21.0" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.0.tgz#5b55c9d394e5fcf304909a8b00c07dc217b56673" integrity sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw== @@ -2048,6 +2048,14 @@ dependencies: "@sinonjs/commons" "^1.7.0" +"@testing-library/cypress@^9.0.0": + version "9.0.0" + resolved "https://registry.yarnpkg.com/@testing-library/cypress/-/cypress-9.0.0.tgz#3facad49c4654a99bbd138f83f33b415d2d6f097" + integrity sha512-c1XiCGeHGGTWn0LAU12sFUfoX3qfId5gcSE2yHode+vsyHDWraxDPALjVnHd4/Fa3j4KBcc5k++Ccy6A9qnkMA== + dependencies: + "@babel/runtime" "^7.14.6" + "@testing-library/dom" "^8.1.0" + "@testing-library/dom@^8.0.0": version "8.19.0" resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-8.19.0.tgz#bd3f83c217ebac16694329e413d9ad5fdcfd785f" @@ -2062,6 +2070,20 @@ lz-string "^1.4.4" pretty-format "^27.0.2" +"@testing-library/dom@^8.1.0": + version "8.20.0" + resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-8.20.0.tgz#914aa862cef0f5e89b98cc48e3445c4c921010f6" + integrity sha512-d9ULIT+a4EXLX3UU8FBjauG9NnsZHkHztXoIcTsOKoOw030fyjheN9svkTULjJxtYag9DZz5Jz5qkWZDPxTFwA== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/runtime" "^7.12.5" + "@types/aria-query" "^5.0.1" + aria-query "^5.0.0" + chalk "^4.1.0" + dom-accessibility-api "^0.5.9" + lz-string "^1.4.4" + pretty-format "^27.0.2" + "@testing-library/jest-dom@^5.16.5": version "5.16.5" resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.16.5.tgz#3912846af19a29b2dbf32a6ae9c31ef52580074e" @@ -2109,6 +2131,11 @@ resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-4.2.2.tgz#ed4e0ad92306a704f9fb132a0cfcf77486dbe2bc" integrity sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig== +"@types/aria-query@^5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-5.0.1.tgz#3286741fb8f1e1580ac28784add4c7a1d49bdfbc" + integrity sha512-XTIieEY+gvJ39ChLcB4If5zHtPxt3Syj5rgZR+e1ctpmK8NjPf0zFqsz4JpLJT0xla9GFDKjy8Cpu331nrmE1Q== + "@types/babel__core@^7.1.14": version "7.1.20" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.20.tgz#e168cdd612c92a2d335029ed62ac94c95b362359"