in the About and Footer components
5 | * Then change those to a Link component
6 | * Then recapture the front-end perf metrics
7 | * The expected result is that with the instant DOM refresh, the latter will be faster
--------------------------------------------------------------------------------
/my-react-app/docs/GETTING-STARTED-REACT.md:
--------------------------------------------------------------------------------
1 | # Getting Started with Create React App
2 |
3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
4 |
5 | ## Available Scripts
6 |
7 | In the project directory, you can run:
8 |
9 | ### `npm start`
10 |
11 | Runs the app in the development mode.\
12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
13 |
14 | The page will reload if you make edits.\
15 | You will also see any lint errors in the console.
16 |
17 | ### `npm test`
18 |
19 | Launches the test runner in the interactive watch mode.\
20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
21 |
22 | ### `npm run build`
23 |
24 | Builds the app for production to the `build` folder.\
25 | It correctly bundles React in production mode and optimizes the build for the best performance.
26 |
27 | The build is minified and the filenames include the hashes.\
28 | Your app is ready to be deployed!
29 |
30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
31 |
32 | ### `npm run eject`
33 |
34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!**
35 |
36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
37 |
38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
39 |
40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
41 |
42 | ## Learn More
43 |
44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
45 |
46 | To learn React, check out the [React documentation](https://reactjs.org/).
47 |
48 | ### Code Splitting
49 |
50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
51 |
52 | ### Analyzing the Bundle Size
53 |
54 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
55 |
56 | ### Making a Progressive Web App
57 |
58 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
59 |
60 | ### Advanced Configuration
61 |
62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
63 |
64 | ### Deployment
65 |
66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
67 |
68 | ### `npm run build` fails to minify
69 |
70 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
71 |
--------------------------------------------------------------------------------
/my-react-app/docs/VISUAL.md:
--------------------------------------------------------------------------------
1 | # Visual E2E Testing
2 |
3 | ## 🧠You will learn
4 |
5 | ✅What is visual E2E testing?
6 |
7 | ✅How to implement visual e2e using WebdriverIO + Screener
8 |
9 | ## How do we check to make sure that the app looks as expected on web and mobile?
10 |
11 | [What is visual testing?](https://docs.google.com/presentation/d/13jYXXoKb36aFt1HLnNnAmsPqw9yaFhVrB4iFH_5_WkI/edit#slide=id.gcc181d5a54_0_284)
12 |
13 | ## A bit about webdriverIO
14 |
15 | * [WebdrverIO](https://webdriver.io/)
16 | * [Screener](https://screener.io/)
17 |
18 | ## Set up a visual test
19 |
20 | 1. Create a new file `my-react-app/test/specs/exercise.spec.js`
21 | 2. Paste the following code
22 |
23 | ```js
24 | describe('My React application', () => {
25 | it('should look correct', () => {
26 | browser.url(`/`);
27 | browser.execute('/*@visual.init*/', 'My React App');
28 | browser.execute('/*@visual.snapshot*/', 'Home Page');
29 | });
30 | });
31 | ```
32 | 3. `npm run test:visual`
33 | 4. View your results in Screener.io
34 |
35 | ### Expand the config to cover iOS and Android
36 |
37 | In today's day and age, everything must be responsive, so let's make sure that our app looks good on iOS web.
38 | Hint, use these capabilities in `wdio.conf.js`:
39 |
40 | ```js
41 | //iphone X
42 | {
43 | browserName: 'safari',
44 | platformName: 'macOS 10.15',
45 | browserVersion: 'latest',
46 | 'sauce:options': {
47 | ...sauceOptions,
48 | },
49 | 'sauce:visual': {
50 | ...visualOptions,
51 | viewportSize: '375x812'
52 | }
53 | }
54 | ```
55 | ---
56 | ### 🏋️♀️❓ Let's change our image, what tests should that break❓
57 | ---
58 |
59 | We're going to update the React image to something better. What tests should break?
60 |
61 | * Drag n drop a new image to the `/src`
62 | * Fix the path to be correct here `import logo from './mia.jpg';` in `App.js`
63 | * Save all files
64 | * Stop the app `ctrl + C`
65 | * Restart the app with `npm start`
66 | * Rerun the visual tests with `npm run test:visual`
67 | * Analyze the results in Screener dashboard
68 |
69 | ❓Is our app fully tested now❓
70 |
71 | | Expected Behavior | Tested? | Test Type | Technologies |
72 | |---|---|---|---|
73 | | Application renders | ✅ | Component | React testing library, Jest |
74 | | Learn React link goes to correct location | ✅ | Component | React testing library, Jest |
75 | | Learn React link opens in new tab | ✅ | Component | React testing library, Jest |
76 | | App looks as expected on web and mobile | ✅ | Visual | Screener,WebdriverIO |
77 | | Front-end performance is at least a B | 🙅♂️ | | |
78 | | App is secure | 🙅♂️ | | |
79 |
80 | ## 📝Summary
81 |
82 | ✅Visual e2e testing is a simple and efficient way to test your web app cross-platform
83 |
84 | ✅We used WebdriverIO + Screener.io to write our visual e2e tests
85 |
86 | Wouldn't it be great to have this tested automatically through CI?
87 |
88 | [Let's setup up CI](./CICD.md)
--------------------------------------------------------------------------------
/my-react-app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "my-react-app",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^5.11.9",
7 | "@testing-library/react": "^11.2.5",
8 | "@testing-library/user-event": "^12.7.3",
9 | "@wdio/cli": "^7.0.7",
10 | "react": "^17.0.1",
11 | "react-dom": "^17.0.1",
12 | "react-scripts": "4.0.3",
13 | "web-vitals": "^1.1.0"
14 | },
15 | "scripts": {
16 | "start": "react-scripts start",
17 | "build": "react-scripts build",
18 | "test": "react-scripts test",
19 | "eject": "react-scripts eject",
20 | "cy:open": "cypress open",
21 | "cy:ci": "cypress run",
22 | "test:visual": "wdio run ./wdio.conf.js",
23 | "test:debug": "node --inspect-brk ./node_modules/jest/bin/jest.js --runInBand --watch"
24 | },
25 | "eslintConfig": {
26 | "extends": [
27 | "react-app",
28 | "react-app/jest"
29 | ]
30 | },
31 | "browserslist": {
32 | "production": [
33 | ">0.2%",
34 | "not dead",
35 | "not op_mini all"
36 | ],
37 | "development": [
38 | "last 1 chrome version",
39 | "last 1 firefox version",
40 | "last 1 safari version"
41 | ]
42 | },
43 | "devDependencies": {
44 | "@wdio/local-runner": "^7.0.7",
45 | "@wdio/mocha-framework": "^7.0.7",
46 | "@wdio/sauce-service": "^7.0.7",
47 | "@wdio/spec-reporter": "^7.0.7",
48 | "@wdio/sync": "^7.0.7",
49 | "chromedriver": "^88.0.0",
50 | "cypress": "7.1.0",
51 | "wdio-chromedriver-service": "^7.0.0"
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/my-react-app/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saucelabs-training/automation-best-practices/82c7279cfa1bc88858b0d4be69332d0a29092a9e/my-react-app/public/favicon.ico
--------------------------------------------------------------------------------
/my-react-app/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 | You need to enable JavaScript to run this app.
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/my-react-app/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saucelabs-training/automation-best-practices/82c7279cfa1bc88858b0d4be69332d0a29092a9e/my-react-app/public/logo192.png
--------------------------------------------------------------------------------
/my-react-app/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saucelabs-training/automation-best-practices/82c7279cfa1bc88858b0d4be69332d0a29092a9e/my-react-app/public/logo512.png
--------------------------------------------------------------------------------
/my-react-app/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/my-react-app/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/my-react-app/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | height: 40vmin;
7 | pointer-events: none;
8 | }
9 |
10 | @media (prefers-reduced-motion: no-preference) {
11 | .App-logo {
12 | animation: App-logo-spin infinite 20s linear;
13 | }
14 | }
15 |
16 | .App-header {
17 | background-color: #282c34;
18 | min-height: 100vh;
19 | display: flex;
20 | flex-direction: column;
21 | align-items: center;
22 | justify-content: center;
23 | font-size: calc(10px + 2vmin);
24 | color: white;
25 | }
26 |
27 | .App-link {
28 | color: #61dafb;
29 | }
30 |
31 | @keyframes App-logo-spin {
32 | from {
33 | transform: rotate(0deg);
34 | }
35 | to {
36 | transform: rotate(360deg);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/my-react-app/src/App.js:
--------------------------------------------------------------------------------
1 | import './App.css';
2 | import logo from './mia.jpg';
3 |
4 | function App() {
5 | return (
6 |
23 | );
24 | }
25 |
26 | export default App;
27 |
--------------------------------------------------------------------------------
/my-react-app/src/__tests__/Exercise.test.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { render, screen } from '@testing-library/react';
3 | import App from '../App';
4 |
5 | //Using Jest matchers: https://jestjs.io/docs/using-matchers
6 | test('renders learn react link', () => {
7 | //render our App component in a virtual DOM
8 | render( );
9 | //search for an element by text
10 | const linkElement = screen.getByTestId('learn-link');
11 | //expect this element to be present in the HTML
12 | expect(linkElement).toBeInTheDocument();
13 | });
14 |
15 | test('link has correct url', () => {
16 | //render our App component in a virtual DOM
17 | render( );
18 | const linkElement = screen.getByTestId('learn-link')
19 | expect(linkElement.href).toContain('ultimateqa');
20 | })
21 |
22 |
--------------------------------------------------------------------------------
/my-react-app/src/__tests__/Solution.test.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { render, screen } from '@testing-library/react';
3 | import App from '../App';
4 |
5 | //Using Jest matchers: https://jestjs.io/docs/using-matchers
6 |
7 | test('renders learn react link', () => {
8 | //render our App component in a virtual DOM
9 | render( );
10 | //search for an element by text
11 | const linkElement = screen.getByText('Learn Testing with Mia')
12 |
13 | //Search for element by test id
14 | //const linkElement = screen.getByTestId('learn-link');
15 |
16 | //Using Jest matchers: https://jestjs.io/docs/using-matchers
17 | //expect this element to be present in the HTML
18 | expect(linkElement).toBeInTheDocument();
19 | })
20 |
21 | test('link has correct url', () => {
22 | //render our App component in a virtual DOM
23 | render( );
24 | const linkElement = screen.getByText('Learn Testing with Mia')
25 | expect(linkElement.href).toContain('ultimateqa');
26 | })
27 |
28 | test('link opens in new tab', () => {
29 | //render our App component in a virtual DOM
30 | render( );
31 | const linkElement = screen.getByText('Learn Testing with Mia')
32 | //Link should open a new tab
33 | expect(linkElement.target).toBe('_blank')
34 | })
35 |
--------------------------------------------------------------------------------
/my-react-app/src/extra-components/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | class App extends Component {
4 |
5 | constructor(props){
6 | super(props);
7 | this.state = {
8 | foo: 'bar',
9 | resumeData: {}
10 | };
11 | }
12 |
13 |
14 | render() {
15 | return (
16 |
17 |
18 | );
19 | }
20 | }
21 |
22 | export default App;
23 |
--------------------------------------------------------------------------------
/my-react-app/src/extra-components/CheckUserAge.js:
--------------------------------------------------------------------------------
1 | //https://www.freecodecamp.org/learn/front-end-libraries/react/use-a-ternary-expression-for-conditional-rendering
2 | const inputStyle = {
3 | width: 235,
4 | margin: 5
5 | };
6 |
7 | class CheckUserAge extends React.Component {
8 | constructor(props) {
9 | super(props);
10 | // Setting a property called 'state'
11 | this.state = {
12 | input:'',
13 | userAge:''
14 | }
15 | this.submit = this.submit.bind(this);
16 | this.handleChange = this.handleChange.bind(this);
17 | }
18 | handleChange(e) {
19 | this.setState({
20 | input: e.target.value,
21 | userAge: ''
22 | });
23 | }
24 | submit() {
25 | this.setState(state => ({
26 | userAge: state.input
27 | }));
28 | }
29 | render() {
30 | const buttonOne = Submit ;
31 | const buttonTwo = You May Enter ;
32 | const buttonThree = You Shall Not Pass ;
33 | return (
34 |
35 |
Enter Your Age to Continue
36 |
42 |
43 | {/* if the userAge is empty, render buttonOne
44 | Otherwise, if the user age is greater than 18 then render buttonTwo, otherwise render buttonThree.
45 | Notice how we are referencing the values in our HTML using this.state */}
46 | {
47 | this.state.userAge === ''
48 | ? buttonOne
49 | : this.state.userAge >= 18
50 | ? buttonTwo
51 | : buttonThree
52 | }
53 |
54 | );
55 | }
56 | }
--------------------------------------------------------------------------------
/my-react-app/src/extra-components/MagicEightBall.js:
--------------------------------------------------------------------------------
1 | import React from 'React';
2 |
3 | const inputStyle = {
4 | width: 235,
5 | margin: 5
6 | };
7 |
8 | class MagicEightBall extends React.Component {
9 | constructor(props) {
10 | super(props);
11 | this.state = {
12 | userInput: '',
13 | randomIndex: ''
14 | };
15 | this.ask = this.ask.bind(this);
16 | this.handleChange = this.handleChange.bind(this);
17 | }
18 | ask() {
19 | if (this.state.userInput) {
20 | this.setState({
21 | //a randomIndex every time the ask() is invoked here
22 | randomIndex: Math.floor(Math.random() * 20),
23 | userInput: ''
24 | });
25 | }
26 | }
27 | handleChange(event) {
28 | this.setState({
29 | userInput: event.target.value
30 | });
31 | }
32 | render() {
33 | const possibleAnswers = [
34 | 'It is certain',
35 | 'It is decidedly so',
36 | 'Without a doubt',
37 | 'Yes, definitely',
38 | 'You may rely on it',
39 | 'As I see it, yes',
40 | 'Outlook good',
41 | 'Yes',
42 | 'Signs point to yes',
43 | 'Reply hazy try again',
44 | 'Ask again later',
45 | 'Better not tell you now',
46 | 'Cannot predict now',
47 | 'Concentrate and ask again',
48 | "Don't count on it",
49 | 'My reply is no',
50 | 'My sources say no',
51 | 'Most likely',
52 | 'Outlook not so good',
53 | 'Very doubtful'
54 | ];
55 | const answer = possibleAnswers[this.state.randomIndex]; // Change this line
56 | return (
57 |
58 |
64 |
65 |
Ask the Magic Eight Ball!
66 |
67 |
Answer:
68 |
69 | {answer}
70 |
71 |
72 | );
73 | }
74 | }
--------------------------------------------------------------------------------
/my-react-app/src/extra-components/MyToDoList.js:
--------------------------------------------------------------------------------
1 | //https://www.freecodecamp.org/learn/front-end-libraries/react/use-array-map-to-dynamically-render-elements
2 | import React, { Component } from 'react';
3 |
4 | const textAreaStyles = {
5 | width: 235,
6 | margin: 5
7 | };
8 |
9 | class MyToDoList extends Component {
10 | constructor(props) {
11 | super(props);
12 | this.state = {
13 | userInput: '',
14 | toDoList: []
15 | }
16 | this.handleSubmit = this.handleSubmit.bind(this);
17 | this.handleChange = this.handleChange.bind(this);
18 | }
19 | handleSubmit() {
20 | const itemsArray = this.state.userInput.split(',');
21 | this.setState({
22 | toDoList: itemsArray
23 | });
24 | }
25 | handleChange(e) {
26 | this.setState({
27 | userInput: e.target.value
28 | });
29 | }
30 | render() {
31 | // Loop over the toDoList array and for each item, returh a with a key and the name of the item
32 | const items = this.state.toDoList.map((item,i) => {item} )
33 | return (
34 |
35 |
41 |
42 |
Create List
43 |
My "To Do" List:
44 |
45 |
46 | );
47 | }
48 | }
49 |
50 | export default MyToDoList;
--------------------------------------------------------------------------------
/my-react-app/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/my-react-app/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import reportWebVitals from './reportWebVitals';
6 |
7 | ReactDOM.render(
8 |
9 |
10 | ,
11 | document.getElementById('root')
12 | );
13 |
14 | // If you want to start measuring performance in your app, pass a function
15 | // to log results (for example: reportWebVitals(console.log))
16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
17 | reportWebVitals();
18 |
--------------------------------------------------------------------------------
/my-react-app/src/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/my-react-app/src/mia.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saucelabs-training/automation-best-practices/82c7279cfa1bc88858b0d4be69332d0a29092a9e/my-react-app/src/mia.jpg
--------------------------------------------------------------------------------
/my-react-app/src/reportWebVitals.js:
--------------------------------------------------------------------------------
1 | const reportWebVitals = onPerfEntry => {
2 | if (onPerfEntry && onPerfEntry instanceof Function) {
3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4 | getCLS(onPerfEntry);
5 | getFID(onPerfEntry);
6 | getFCP(onPerfEntry);
7 | getLCP(onPerfEntry);
8 | getTTFB(onPerfEntry);
9 | });
10 | }
11 | };
12 |
13 | export default reportWebVitals;
14 |
--------------------------------------------------------------------------------
/my-react-app/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/my-react-app/test/specs/visual.solution.spec.js:
--------------------------------------------------------------------------------
1 | describe('My React application', () => {
2 | it('should look correct', () => {
3 | browser.url(`/`);
4 | browser.execute('/*@visual.init*/', 'My React App');
5 | browser.execute('/*@visual.snapshot*/', 'Home Page');
6 | });
7 | });
8 |
9 |
--------------------------------------------------------------------------------
/my-react-app/wdio.conf.js:
--------------------------------------------------------------------------------
1 | const visualOptions = {
2 | apiKey: process.env.SCREENER_API_KEY,
3 | projectName: 'new-app',
4 | scrollAndStitchScreenshots: true
5 | };
6 | const sauceOptions = {
7 | username: process.env.SAUCE_USERNAME,
8 | accesskey: process.env.SAUCE_ACCESS_KEY
9 | };
10 |
11 | exports.config = {
12 | runner: 'local',
13 | user: process.env.SAUCE_USERNAME,
14 | key: process.env.SAUCE_ACCESS_KEY,
15 | region: 'us',
16 | services: [
17 | ['sauce', {
18 | sauceConnect: true
19 | }]
20 | ],
21 | specs: [
22 | './test/specs/**/*.js'
23 | ],
24 | // Patterns to exclude.
25 | exclude: [
26 | // 'path/to/excluded/files'
27 | ],
28 | //
29 | // ============
30 | // Capabilities
31 | // ============
32 | // Define your capabilities here. WebdriverIO can run multiple capabilities at the same
33 | // time. Depending on the number of capabilities, WebdriverIO launches several test
34 | // sessions. Within your capabilities you can overwrite the spec and exclude options in
35 | // order to group specific specs to a specific capability.
36 | //
37 | // First, you can define how many instances should be started at the same time. Let's
38 | // say you have 3 different capabilities (Chrome, Firefox, and Safari) and you have
39 | // set maxInstances to 1; wdio will spawn 3 processes. Therefore, if you have 10 spec
40 | // files and you set maxInstances to 10, all spec files will get tested at the same time
41 | // and 30 processes will get spawned. The property handles how many capabilities
42 | // from the same test should run tests.
43 | //
44 | maxInstances: 100,
45 | //Screener config
46 | hostname: 'hub.screener.io',
47 | port: 443,
48 | protocol: 'https',
49 | path: '/wd/hub',
50 | capabilities: [
51 | //Desktop A 28%: https://www.w3schools.com/browsers/browsers_display.asp
52 | {
53 | browserName: 'chrome',
54 | platformName: 'windows 10',
55 | browserVersion: 'latest',
56 | 'sauce:options': {
57 | ...sauceOptions,
58 | },
59 | 'sauce:visual':{
60 | ...visualOptions
61 | }
62 | },
63 | {
64 | browserName: 'safari',
65 | platformName: 'macOS 10.15',
66 | browserVersion: 'latest',
67 | 'sauce:options': {
68 | ...sauceOptions,
69 | },
70 | 'sauce:visual': {
71 | ...visualOptions,
72 | viewportSize: '375x812'
73 | }
74 | }
75 | ],
76 | // Level of logging verbosity: trace | debug | info | warn | error | silent
77 | logLevel: 'debug',
78 | // bail (default is 0 - don't bail, run all tests).
79 | bail: 0,
80 | baseUrl: 'http://localhost:3000',
81 | //
82 | // Default timeout for all waitFor* commands.
83 | waitforTimeout: 10000,
84 | //
85 | // Default timeout in milliseconds for request
86 | // if browser driver or grid doesn't send response
87 | connectionRetryTimeout: 120000,
88 | //
89 | // Default request retries count
90 | connectionRetryCount: 3,
91 |
92 | framework: 'mocha',
93 | reporters: ['spec'],
94 | //
95 | // Options to be passed to Mocha.
96 | // See the full list at http://mochajs.org/
97 | mochaOpts: {
98 | ui: 'bdd',
99 | timeout: 60000
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "automation-best-practices",
3 | "version": "1.0.0",
4 | "lockfileVersion": 2,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "automation-best-practices",
9 | "version": "1.0.0",
10 | "license": "MIT"
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "automation-best-practices",
3 | "version": "1.0.0",
4 | "description": "This is an Automation Best Practices workshop designed to teach testing through cross-functional automation",
5 | "main": "index.js",
6 | "repository": {
7 | "type": "git",
8 | "url": "git+https://github.com/saucelabs-training/automation-best-practices.git"
9 | },
10 | "keywords": [],
11 | "author": "Nikolay Advolodkin @nikolay_a00",
12 | "license": "MIT",
13 | "bugs": {
14 | "url": "https://github.com/saucelabs-training/automation-best-practices/issues"
15 | },
16 | "homepage": "https://github.com/saucelabs-training/automation-best-practices#readme"
17 | }
18 |
--------------------------------------------------------------------------------
/tado-sec/README.md:
--------------------------------------------------------------------------------
1 | # Testing with WebdriverIO on Sauce
2 |
3 | ## 🧠You will learn
4 |
5 | ✅sauce labs dashboard
6 |
7 | ✅functional ui tests with WebdriverIO
8 |
9 | ✅how to run tests in sauce labs ci grid
10 |
11 | ✅how to run tests against a local build
12 |
13 | ✅how to run tests cross-browser and cross-platform
14 |
15 | ## 🔧Technologies you will use
16 |
17 | 1. ReactJS
18 | 2. WebdriverIO v7 async
19 | 3. Sauce Labs
20 | 4. Sauce Connect
21 | 5. Node.JS
22 |
23 | ## Table Of Contents
24 |
25 | - [Test local apps in Sauce](./my-react-app/solution/docs/LOCAL-SAUCE-TESTS.md)
26 | - [Sauce UI](https://accounts.saucelabs.com/am/XUI/#login/)
27 | - [Sauce connect](https://docs.saucelabs.com/secure-connections/sauce-connect/)
28 | - [Cross-platform testing](./my-react-app/solution/docs/CROSS-PLATFORM.md)
29 | - [Super important, must-have resources](./my-react-app/solution/docs/CONCLUSIONS.md)
30 |
31 | ## Prerequisites
32 |
33 | - **[🚨Setup steps completed before the workshop🚨](#setup)**
34 | - Node.JS installed
35 | - Sauce Labs account
36 | - Basic understanding of JavaScript
37 | - Your favorite IDE (We will use [VSCode](https://code.visualstudio.com/Download))
38 |
39 | ## Key
40 |
41 | 💡 this is a tip
42 |
43 | 🏋️♀️ this is an exercise for you to do
44 |
45 | ❓ this is a question for us to think and talk about. Try not to scroll beyond this question before we discuss
46 |
47 | ## Your Instructor: Nikolay Advolodkin
48 |
49 |
50 |
51 | - 🏢 I’m a Sr Solutions Architect at Sauce Labs
52 | - 🔭 I’m the founder of [Ultimate QA](https://ultimateqa.com/)
53 | - 🌱 I’m currently working on [Sauce Bindings](https://github.com/saucelabs/sauce_bindings)
54 | - 💬 Ask me about environmentalism, veganism, test automation, and fitness
55 | - 😄 Pronouns: he/him
56 | - ⚡ Fun fact: I'm a vegan that's super pasionate about saving the planet, saving animals, and helping underpriveleged communities
57 | - 📫 Follow me for testing and dev training
58 | - [JS Testing Newsletter](https://ultimateqa.ck.page/js-testing-tips)
59 | - [Youtube](https://youtube.com/ultimateqa)
60 | - [LinkedIn](https://www.linkedin.com/in/nikolayadvolodkin/)
61 | - [Twitter](https://twitter.com/Nikolay_A00)
62 |
63 | ## Setup
64 |
65 | ### 1. Install Node 14 LTS
66 |
67 | Please follow your organizations installation instructions
68 |
69 | ### 2.Clone or download the repo
70 |
71 | ```bash
72 | git clone https://github.com/saucelabs-training/automation-best-practices
73 | ```
74 |
75 | ### 3.Navigate to the directory of where you cloned your repo
76 |
77 | ```bash
78 | #pwd
79 | cd YOUR_FORK_DIR/automation-best-practices/tado-sec/my-react-app
80 | ```
81 |
82 | ### 4.Install the app
83 |
84 | ```bash
85 | npm install
86 | npm start
87 | ```
88 |
89 |
90 |
91 |
92 | Click here to see an example output.
93 |
94 |
95 |
96 |
97 | Compiled successfully!
98 |
99 | You can now view my-react-app in the browser.
100 |
101 | Local: http://localhost:3000
102 | On Your Network: http://172.20.10.2:3000
103 |
104 | Note that the development build is not optimized.
105 | To create a production build, use npm run build.
106 |
107 |
108 |
109 |
110 |
111 |
112 | **Don't worry about fixing any warnings that may appear during `npm install`**
113 |
114 | Once the app is running, you can stop it with `Ctrl + C` in command line. We don't need the app running right now.
115 |
116 | ### 5.Set Your Sauce Labs Credentials
117 |
118 | > It's a best practice to store 3rd party credentials in your
119 | > system environment variables follow instructions 👇
120 |
121 | - [MacOS/Linux](https://docs.saucelabs.com/basics/environment-variables/#setting-up-environment-variables-on-macos-and-linux-systems)
122 | - [Windows](https://docs.saucelabs.com/basics/environment-variables/#setting-up-environment-variables-on-windows-systems)
123 |
124 | > If you don't have access to set your environment variable credentials, then just hardcode them in `test/configs/wdio.sanity.sauce.conf.js` 👇
125 |
126 | ```js
127 | exports.config = {
128 | runner: 'local',
129 | user: 'SAUCE_USERNAME',
130 | key: 'SAUCE_ACCESS_KEY',
131 | ```
132 |
133 | ### 6. Run tests
134 |
135 | ```bash
136 | npm run test.sanity
137 | ```
138 |
139 |
140 |
141 |
142 | Click here to see an example output.
143 |
144 |
145 |
146 |
147 | wdio run test/configs/wdio.sanity.conf.js
148 |
149 | Execution of 1 workers started at 2022-01-28T19:20:01.341Z
150 |
151 | [0-0] RUNNING in chrome - /test/specs/sanity.spec.js
152 | [0-0] PASSED in chrome - /test/specs/sanity.spec.js
153 |
154 |
155 |
156 |
157 |
158 |
159 | #### ✅👏Congratulations, your environment is ready!
160 |
--------------------------------------------------------------------------------
/tado-sec/my-react-app/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/tado-sec/my-react-app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "my-react-app",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^5.14.1",
7 | "@testing-library/react": "^12.1.0",
8 | "@testing-library/user-event": "^13.2.1",
9 | "@wdio/cli": "^7.13.0",
10 | "react": "^17.0.2",
11 | "react-dom": "^17.0.2",
12 | "react-scripts": "4.0.3",
13 | "web-vitals": "^2.1.0"
14 | },
15 | "scripts": {
16 | "start": "react-scripts start",
17 | "build": "react-scripts build",
18 | "test": "react-scripts test",
19 | "eject": "react-scripts eject",
20 | "test.sanity": "wdio test/configs/wdio.sanity.sauce.conf.js",
21 | "test.sanity.ci": "wdio test/configs/wdio.sanity.sauce.conf.js --logLevel debug",
22 | "test.sauce.eu": "REGION=eu wdio test/configs/wdio.sanity.sauce.conf.js",
23 | "test.sauce.all": "wdio test/configs/wdio.cross.platform.sauce.conf.js",
24 | "test.sauce.local": "wdio test/configs/wdio.localhost.sauce.conf.js",
25 | "test.debug": "node --inspect-brk ./node_modules/jest/bin/jest.js --runInBand --watch"
26 | },
27 | "eslintConfig": {
28 | "extends": [
29 | "react-app",
30 | "react-app/jest"
31 | ]
32 | },
33 | "browserslist": {
34 | "production": [
35 | ">0.2%",
36 | "not dead",
37 | "not op_mini all"
38 | ],
39 | "development": [
40 | "last 1 chrome version",
41 | "last 1 firefox version",
42 | "last 1 safari version"
43 | ]
44 | },
45 | "devDependencies": {
46 | "@wdio/jasmine-framework": "^7.16.13",
47 | "@wdio/local-runner": "^7.16.13",
48 | "@wdio/sauce-service": "^7.13.0",
49 | "@wdio/spec-reporter": "^7.16.13"
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/tado-sec/my-react-app/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saucelabs-training/automation-best-practices/82c7279cfa1bc88858b0d4be69332d0a29092a9e/tado-sec/my-react-app/public/favicon.ico
--------------------------------------------------------------------------------
/tado-sec/my-react-app/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 | You need to enable JavaScript to run this app.
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/tado-sec/my-react-app/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saucelabs-training/automation-best-practices/82c7279cfa1bc88858b0d4be69332d0a29092a9e/tado-sec/my-react-app/public/logo192.png
--------------------------------------------------------------------------------
/tado-sec/my-react-app/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saucelabs-training/automation-best-practices/82c7279cfa1bc88858b0d4be69332d0a29092a9e/tado-sec/my-react-app/public/logo512.png
--------------------------------------------------------------------------------
/tado-sec/my-react-app/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/tado-sec/my-react-app/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/tado-sec/my-react-app/solution/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/tado-sec/my-react-app/solution/docs/CONCLUSIONS.md:
--------------------------------------------------------------------------------
1 | # before you go!
2 |
3 |
4 |
5 | [👉 Please leave your quick feedback on this form, it really helps future workshops 🙏](https://docs.google.com/forms/d/e/1FAIpQLSeKMcxoEqmuHAVp_bwseXahN6z844t-2wPJpkuQPz6LfemLjg/viewform?usp=sf_link)
6 |
7 | ## Extra resources
8 |
9 | - [🥇 Demo JS Repo, the holy grail of Sauce examples](https://github.com/saucelabs-training/demo-js)
10 | - [Sauce docs](https://docs.saucelabs.com/web-apps/automated-testing/selenium/)
11 | - [Webdriver IO docs](https://webdriver.io/docs/gettingstarted)
12 | - [🎦 My video tutorials on how to test web apps with JS](https://www.youtube.com/playlist?list=PLSRQwlkmpdj5ak1Rxahdo6mguhbcCOePR)
13 | - [Automation patterns and anti-patters](https://ultimateqa.com/automation-patterns-antipatterns)
14 |
15 | 📫 Contact me if you have other questions
16 |
17 | - [LinkedIn](https://www.linkedin.com/in/nikolayadvolodkin/)
18 | - [Twitter](https://twitter.com/Nikolay_A00)
19 |
--------------------------------------------------------------------------------
/tado-sec/my-react-app/solution/docs/CROSS-PLATFORM.md:
--------------------------------------------------------------------------------
1 | # TESTING CROSS-PLATFORM AND CROSS-BROWSER
2 |
3 | ## 🧠You will learn
4 |
5 | ✅ How to run tests cross-platform on web and mobile
6 |
7 | ✅ How to run tests in parallel
8 |
9 | ## 🏋️♀️ Work to run tests cross-browser and cross-platform
10 |
11 | 1. Create a new test file `cross.platform.spec.js` in `/test/specs` and paste the code below
12 |
13 | ```js
14 | describe('Sauce Demo is flaky', () => {
15 | it('loads', async () => {
16 | // 1. Go to the home page
17 | await browser.url('');
18 | // 2. Create an element variable
19 | const elem = await $('#login-button');
20 | // 3. Verify the element is displayed
21 | await elem.waitForDisplayed();
22 | });
23 | it('fails', async () => {
24 | await browser.url('');
25 | const elem = await $('#log-button');
26 | await elem.waitForDisplayed();
27 | });
28 | });
29 | ```
30 |
31 | 2. Create a new config file `wdio.cross.platform.sauce.conf.js` in `/test/configs`
32 | 3. Copy the content from [here](../../solution/test/configs/wdio.cross.platform.sauce.conf.js) into your new file
33 | 4. Let's understand the new config
34 | 5. Add a script to `package.json` `"test.sauce.all": "wdio test/configs/wdio.cross.platform.sauce.conf.js"`
35 | 6. Run the tests
36 |
37 | ---
38 |
39 | ## ❓ Why are the tests failing?
40 |
41 | ---
42 |
43 | ---
44 |
45 | ## ❓How would you also run this test on Safari?
46 |
47 | ---
48 |
49 | ---
50 |
51 | ## ❓ What about on a real mobile device like iPhone XS?
52 |
53 | ---
54 |
55 | ---
56 |
57 | ## ❓ What about on a real mobile device like Samsung Galaxy S10?
58 |
59 | ---
60 |
61 | ---
62 |
63 | ## What if you wanted devices matching a certain criteria?
64 |
65 | ---
66 |
67 | ```js
68 | {
69 | name: 'Run on deviceName regex iOS - iPhone [678]?.*',
70 | browserName: 'safari',
71 | deviceName: 'iPhone [678]?.*',
72 | platformName: 'iOS',
73 | // https://docs.saucelabs.com/dev/test-configuration-options/#cacheid
74 | cacheId: 'vvqb5g7lr3', // See the capabilities url as mentioned above
75 | // Specs are not mentioned here so it will run all tests
76 | // from ./test/specs/
77 | build,
78 | },
79 | {
80 | name: 'Run on major iOS version - 13',
81 | browserName: 'safari',
82 | platformName: 'iOS',
83 | platformVersion: '13',
84 | // Extra caps
85 | cacheId: 'bq9jvoctq7', // See the capabilities url as mentioned above
86 | build,
87 | },
88 | {
89 | name: 'Run iOS phone only',
90 | browserName: 'safari',
91 | platformName: 'iOS',
92 | phoneOnly: true,
93 | // Extra caps
94 | cacheId: 'q96a2zipwp', // See the capabilities url as mentioned above
95 | build,
96 | },
97 | ```
98 |
99 | Run the tests with `npm run test.sauce.all`
100 |
101 | ## 🏋️♀️ Let's run in parallel
102 |
103 | 1. Duplicate `cross.platform.spec`
104 | 2. Leave only the passing test
105 | 3. Update the appropriate `.conf.js`
106 | 4. Run tests using `npm run test.sauce.all`
107 |
108 | **🚀 Congratulations, on your journey! 💃**
109 |
110 |
111 |
112 | ## Questions?
113 |
114 | ## ⏭️ Let's finish up
115 |
--------------------------------------------------------------------------------
/tado-sec/my-react-app/solution/docs/LOCAL-SAUCE-TESTS.md:
--------------------------------------------------------------------------------
1 | # TESTING AN APP INSIDE OF YOUR NETWORK
2 |
3 | ## 🧠You will learn
4 |
5 | ✅ How to test a local web application with functional browser tests (aka E2E tests)
6 |
7 | ✅ Use WebdriverIO
8 |
9 | ✅ Use Sauce Connect
10 |
11 | ## ⚙️ Setup
12 |
13 | 1. Stop all servers from previous session (`Ctrl + C` everything)
14 | 2. cd `my-react-app`
15 | 3. `npm start`
16 |
17 | Open application at http://localhost:3000/
18 |
19 | Explore the functionality of the app
20 |
21 | ## 🏋️♀️ Together, let's code your first WebdriverIO test!
22 |
23 | Our test will open the application and make sure it renders
24 |
25 | 1. Write the test in `test/specs/localhost.spec.js`
26 |
27 | ```js
28 | // https://webdriver.io/docs/api
29 | describe('My local react app', () => {
30 | it('renders', async () => {
31 | // navigate to the default url found in wdio.conf.js
32 | await browser.url('/');
33 | // create image variable
34 | const image = await $('[data-testid="main-img"]');
35 | // check if image is displayed
36 | await image.waitForDisplayed();
37 | });
38 | });
39 | ```
40 |
41 | 2. Look at `test/configs/wdio.localhost.sauce.conf.js`
42 | 3. Go to `package.json` and add a `script` `"test.local.sauce": "wdio test/configs/wdio.localhost.sauce.conf.js"`
43 | 4. Now run your tests with `npm run test.sauce.local` in a new terminal. **Do not stop your app server!**
44 |
45 | **🚀 Congratulations, on your WebdriverIO functional test!💃**
46 |
47 | ## 📝Summary
48 |
49 | 1. Use WebdriverIO to do functional testing on your apps
50 | 2. Use [Sauce Connect](https://docs.saucelabs.com/secure-connections/sauce-connect/) to test an app on an internal environment
51 | 3. A `conf.js` file is used to configure WebdriverIO
52 | 4. A `package.json` is where we put our scripts to easily run different things
53 |
54 | ## ⏭️ Let's understand what happened in Sauce when we ran the test
55 |
--------------------------------------------------------------------------------
/tado-sec/my-react-app/solution/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "my-react-app",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^5.14.1",
7 | "@testing-library/react": "^12.1.0",
8 | "@testing-library/user-event": "^13.2.1",
9 | "@wdio/cli": "^7.13.0",
10 | "react": "^17.0.2",
11 | "react-dom": "^17.0.2",
12 | "react-scripts": "4.0.3",
13 | "web-vitals": "^2.1.0"
14 | },
15 | "scripts": {
16 | "start": "react-scripts start",
17 | "build": "react-scripts build",
18 | "test": "react-scripts test",
19 | "eject": "react-scripts eject",
20 | "test.sanity": "wdio test/configs/wdio.sanity.sauce.conf.js",
21 | "test.sanity.ci": "wdio test/configs/wdio.sanity.sauce.conf.js --logLevel debug",
22 | "test.sauce.eu": "REGION=eu wdio test/configs/wdio.sanity.sauce.conf.js",
23 | "test.sauce.all": "wdio test/configs/wdio.cross.platform.sauce.conf.js",
24 | "test.sauce.local": "wdio test/configs/wdio.localhost.sauce.conf.js",
25 | "test.debug": "node --inspect-brk ./node_modules/jest/bin/jest.js --runInBand --watch"
26 | },
27 | "eslintConfig": {
28 | "extends": [
29 | "react-app",
30 | "react-app/jest"
31 | ]
32 | },
33 | "browserslist": {
34 | "production": [
35 | ">0.2%",
36 | "not dead",
37 | "not op_mini all"
38 | ],
39 | "development": [
40 | "last 1 chrome version",
41 | "last 1 firefox version",
42 | "last 1 safari version"
43 | ]
44 | },
45 | "devDependencies": {
46 | "@wdio/jasmine-framework": "^7.16.13",
47 | "@wdio/local-runner": "^7.16.13",
48 | "@wdio/sauce-service": "^7.13.0",
49 | "@wdio/spec-reporter": "^7.16.13"
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/tado-sec/my-react-app/solution/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saucelabs-training/automation-best-practices/82c7279cfa1bc88858b0d4be69332d0a29092a9e/tado-sec/my-react-app/solution/public/favicon.ico
--------------------------------------------------------------------------------
/tado-sec/my-react-app/solution/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 | You need to enable JavaScript to run this app.
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/tado-sec/my-react-app/solution/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saucelabs-training/automation-best-practices/82c7279cfa1bc88858b0d4be69332d0a29092a9e/tado-sec/my-react-app/solution/public/logo192.png
--------------------------------------------------------------------------------
/tado-sec/my-react-app/solution/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saucelabs-training/automation-best-practices/82c7279cfa1bc88858b0d4be69332d0a29092a9e/tado-sec/my-react-app/solution/public/logo512.png
--------------------------------------------------------------------------------
/tado-sec/my-react-app/solution/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/tado-sec/my-react-app/solution/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/tado-sec/my-react-app/solution/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | height: 40vmin;
7 | pointer-events: none;
8 | }
9 |
10 | @media (prefers-reduced-motion: no-preference) {
11 | .App-logo {
12 | /*animation: App-logo-spin infinite 20s linear;*/
13 | }
14 | }
15 |
16 | .App-header {
17 | background-color: #282c34;
18 | min-height: 100vh;
19 | display: flex;
20 | flex-direction: column;
21 | align-items: center;
22 | justify-content: center;
23 | font-size: calc(10px + 2vmin);
24 | color: white;
25 | }
26 |
27 | .App-link {
28 | color: #61dafb;
29 | }
30 |
31 | @keyframes App-logo-spin {
32 | from {
33 | transform: rotate(0deg);
34 | }
35 | to {
36 | transform: rotate(360deg);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/tado-sec/my-react-app/solution/src/App.js:
--------------------------------------------------------------------------------
1 | import './App.css';
2 | import logo from './mia-me.jpg';
3 |
4 | function App() {
5 | return (
6 |
28 | );
29 | }
30 |
31 | export default App;
32 |
--------------------------------------------------------------------------------
/tado-sec/my-react-app/solution/src/__tests__/App.test.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { render, screen } from '@testing-library/react';
3 | import App from '../App';
4 |
5 | //Using Jest matchers: https://jestjs.io/docs/using-matchers
6 |
7 | test('renders learn react link', () => {
8 | //render our App component in a virtual DOM
9 | render( );
10 | //search for an element by text
11 | const linkElement = screen.getByText('Learn React')
12 | //expect this element to be present in the HTML
13 | expect(linkElement).toBeInTheDocument();
14 | })
--------------------------------------------------------------------------------
/tado-sec/my-react-app/solution/src/__tests__/Exercise.test.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { render, screen } from '@testing-library/react';
3 | import App from '../App';
4 |
5 | //Using Jest matchers: https://jestjs.io/docs/using-matchers
6 | test('link has correct url', () => {
7 | /** Your code below */
8 |
9 | //1. render our App component in a virtual DOM
10 | render( );
11 | //2. search for an element by text
12 | const linkElement = screen.getByText('Learn React')
13 | //3. search for href
14 | expect(linkElement.href).toContain('ultimateqa');
15 |
16 | /** Your code above */
17 | })
18 |
19 | test('link opens in new tab', () => {
20 | /** Your code below */
21 |
22 | //1. render our App component in a virtual DOM
23 | //2. get the link element
24 | //3. expect `linkElement.target` toBe('_blank')
25 |
26 | /** Your code above */
27 | })
28 |
29 |
--------------------------------------------------------------------------------
/tado-sec/my-react-app/solution/src/__tests__/Solution.test.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { render, screen } from '@testing-library/react';
3 | import App from '../App';
4 |
5 | //Using Jest matchers: https://jestjs.io/docs/using-matchers
6 |
7 | test('renders learn react link', () => {
8 | //render our App component in a virtual DOM
9 | render( );
10 | //search for an element by text
11 | const linkElement = screen.getByText('Learn Testing with Mia')
12 |
13 | //Search for element by test id
14 | //const linkElement = screen.getByTestId('learn-link');
15 |
16 | //Using Jest matchers: https://jestjs.io/docs/using-matchers
17 | //expect this element to be present in the HTML
18 | expect(linkElement).toBeInTheDocument();
19 | })
20 |
21 | test('link has correct url', () => {
22 | //render our App component in a virtual DOM
23 | render( );
24 | const linkElement = screen.getByText('Learn Testing with Mia')
25 | expect(linkElement.href).toContain('ultimateqa');
26 | })
27 |
28 | test('link opens in new tab', () => {
29 | //render our App component in a virtual DOM
30 | render( );
31 | const linkElement = screen.getByText('Learn Testing with Mia')
32 | //Link should open a new tab
33 | expect(linkElement.target).toBe('_blank')
34 | })
35 |
--------------------------------------------------------------------------------
/tado-sec/my-react-app/solution/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/tado-sec/my-react-app/solution/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import reportWebVitals from './reportWebVitals';
6 |
7 | ReactDOM.render(
8 |
9 |
10 | ,
11 | document.getElementById('root')
12 | );
13 |
14 | // If you want to start measuring performance in your app, pass a function
15 | // to log results (for example: reportWebVitals(console.log))
16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
17 | reportWebVitals();
18 |
--------------------------------------------------------------------------------
/tado-sec/my-react-app/solution/src/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tado-sec/my-react-app/solution/src/mia-me.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saucelabs-training/automation-best-practices/82c7279cfa1bc88858b0d4be69332d0a29092a9e/tado-sec/my-react-app/solution/src/mia-me.jpg
--------------------------------------------------------------------------------
/tado-sec/my-react-app/solution/src/mia.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saucelabs-training/automation-best-practices/82c7279cfa1bc88858b0d4be69332d0a29092a9e/tado-sec/my-react-app/solution/src/mia.jpg
--------------------------------------------------------------------------------
/tado-sec/my-react-app/solution/src/mia2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saucelabs-training/automation-best-practices/82c7279cfa1bc88858b0d4be69332d0a29092a9e/tado-sec/my-react-app/solution/src/mia2.jpg
--------------------------------------------------------------------------------
/tado-sec/my-react-app/solution/src/reportWebVitals.js:
--------------------------------------------------------------------------------
1 | const reportWebVitals = onPerfEntry => {
2 | if (onPerfEntry && onPerfEntry instanceof Function) {
3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4 | getCLS(onPerfEntry);
5 | getFID(onPerfEntry);
6 | getFCP(onPerfEntry);
7 | getLCP(onPerfEntry);
8 | getTTFB(onPerfEntry);
9 | });
10 | }
11 | };
12 |
13 | export default reportWebVitals;
14 |
--------------------------------------------------------------------------------
/tado-sec/my-react-app/solution/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/tado-sec/my-react-app/solution/test/configs/wdio.cross.platform.sauce.conf.js:
--------------------------------------------------------------------------------
1 | // https://webdriver.io/docs/gettingstarted
2 |
3 | //add a build name to the sauce labs jobs
4 | const build = `WebdriverIO cross platform build-${new Date().getTime()}`;
5 |
6 | exports.config = {
7 | runner: 'local',
8 | user: process.env.SAUCE_USERNAME,
9 | key: process.env.SAUCE_ACCESS_KEY,
10 | //default region is US unless set in command line
11 | region: process.env.REGION || 'us',
12 | specs: ['./test/specs/**/cross.platform.*'],
13 | // Patterns to exclude.
14 | exclude: ['./test/specs/**/sanity.spec.js'],
15 | //
16 | // ============
17 | // Capabilities
18 | // ============
19 | maxInstances: 100,
20 | // ===================================================================================
21 | // Capabilities
22 | // You can find more about constructing the capabilities for real device testing here
23 | // https://docs.saucelabs.com/dev/test-configuration-options/
24 | // https://saucelabs.com/platform/platform-configurator
25 | // ===================================================================================
26 | capabilities: [
27 | {
28 | browserName: 'chrome',
29 | platformName: 'windows 10',
30 | browserVersion: 'latest',
31 | },
32 | // {
33 | // name: 'Run on device description for iOS',
34 | // browserName: 'safari',
35 | // deviceName: 'iPhone XS',
36 | // platformName: 'iOS',
37 | // build,
38 | // },
39 | // {
40 | // name: 'Run on device description for Android',
41 | // browserName: 'chrome',
42 | // deviceName: 'Samsung Galaxy S10',
43 | // platformName: 'Android',
44 | // build,
45 | // },
46 | // /**
47 | // * Capabilities to run on a 'random' device based on a regular expression
48 | // * This will give a device based on:
49 | // * - matching regular expression
50 | // * - direct availability
51 | // */
52 | // {
53 | // name: 'Run on deviceName regex iOS - iPhone [678]?.*',
54 | // browserName: 'safari',
55 | // deviceName: 'iPhone [678]?.*',
56 | // platformName: 'iOS',
57 | // // https://docs.saucelabs.com/dev/test-configuration-options/#cacheid
58 | // cacheId: 'vvqb5g7lr3', // See the capabilities url as mentioned above
59 | // // Specs are not mentioned here so it will run all tests
60 | // // from ./test/specs/
61 | // build,
62 | // },
63 | // {
64 | // name: 'Run on major iOS version - 13',
65 | // browserName: 'safari',
66 | // platformName: 'iOS',
67 | // platformVersion: '13',
68 | // // Extra caps
69 | // cacheId: 'bq9jvoctq7', // See the capabilities url as mentioned above
70 | // build,
71 | // },
72 | // {
73 | // name: 'Run iOS phone only',
74 | // browserName: 'safari',
75 | // platformName: 'iOS',
76 | // phoneOnly: true,
77 | // // Extra caps
78 | // cacheId: 'q96a2zipwp', // See the capabilities url as mentioned above
79 | // build,
80 | // },
81 | ],
82 | // Level of logging verbosity: trace | debug | info | warn | error | silent
83 | logLevel: 'error',
84 | // bail (default is 0 - don't bail, run all tests).
85 | bail: 0,
86 | baseUrl: 'https://www.saucedemo.com',
87 | //
88 | // Default timeout for all waitFor* commands.
89 | waitforTimeout: 10000,
90 | //
91 | // Default timeout in milliseconds for request
92 | // if browser driver or grid doesn't send response
93 | connectionRetryTimeout: 120000,
94 | //
95 | // Default request retries count
96 | connectionRetryCount: 3,
97 |
98 | framework: 'jasmine',
99 | reporters: ['spec'],
100 | services: ['sauce'],
101 | jasmineOpts: {
102 | defaultTimeoutInterval: 120000,
103 | },
104 | };
105 |
--------------------------------------------------------------------------------
/tado-sec/my-react-app/solution/test/configs/wdio.localhost.sauce.conf.js:
--------------------------------------------------------------------------------
1 | // https://webdriver.io/docs/gettingstarted
2 | exports.config = {
3 | runner: 'local',
4 | user: process.env.SAUCE_USERNAME,
5 | key: process.env.SAUCE_ACCESS_KEY,
6 | region: process.env.REGION || 'us',
7 | services: [
8 | [
9 | 'sauce',
10 | {
11 | sauceConnect: true,
12 | sauceConnectOpts: {
13 | noSslBumpDomains: 'all',
14 | },
15 | },
16 | ],
17 | ],
18 | specs: ['./test/specs/**/localhost.spec.js'],
19 | // Patterns to exclude.
20 | exclude: ['./test/specs/**/sanity.spec.js'],
21 | //
22 | // ============
23 | // Capabilities
24 | // ============
25 | maxInstances: 100,
26 | capabilities: [
27 | //Desktop A 28%: https://www.w3schools.com/browsers/browsers_display.asp
28 | {
29 | browserName: 'chrome',
30 | platformName: 'windows 10',
31 | browserVersion: 'latest',
32 | },
33 | ],
34 | // Level of logging verbosity: trace | debug | info | warn | error | silent
35 | logLevel: 'error',
36 | // bail (default is 0 - don't bail, run all tests).
37 | bail: 0,
38 | baseUrl: 'http://localhost:3000',
39 | //
40 | // Default timeout for all waitFor* commands.
41 | waitforTimeout: 10000,
42 | //
43 | // Default timeout in milliseconds for request
44 | // if browser driver or grid doesn't send response
45 | connectionRetryTimeout: 120000,
46 | //
47 | // Default request retries count
48 | connectionRetryCount: 3,
49 |
50 | framework: 'jasmine',
51 | reporters: ['spec'],
52 | services: ['sauce'],
53 | jasmineOpts: {
54 | defaultTimeoutInterval: 120000,
55 | },
56 | };
57 |
--------------------------------------------------------------------------------
/tado-sec/my-react-app/solution/test/configs/wdio.sanity.sauce.conf.js:
--------------------------------------------------------------------------------
1 | exports.config = {
2 | runner: 'local',
3 | user: process.env.SAUCE_USERNAME,
4 | key: process.env.SAUCE_ACCESS_KEY,
5 | region: 'us',
6 | services: [
7 | [
8 | 'sauce',
9 | {
10 | sauceConnect: true,
11 | sauceConnectOpts: {
12 | noSslBumpDomains: 'all',
13 | },
14 | },
15 | ],
16 | ],
17 | specs: ['./test/specs/**/sanity.spec.js'],
18 | // Patterns to exclude.
19 | exclude: [
20 | // 'path/to/excluded/files'
21 | ],
22 | //
23 | // ============
24 | // Capabilities
25 | // ============
26 | maxInstances: 100,
27 | capabilities: [
28 | //Desktop A 28%: https://www.w3schools.com/browsers/browsers_display.asp
29 | {
30 | browserName: 'chrome',
31 | platformName: 'windows 10',
32 | browserVersion: 'latest',
33 | },
34 | ],
35 | // Level of logging verbosity: trace | debug | info | warn | error | silent
36 | logLevel: 'error',
37 | // bail (default is 0 - don't bail, run all tests).
38 | bail: 0,
39 | baseUrl: 'https://www.saucedemo.com',
40 | //
41 | // Default timeout for all waitFor* commands.
42 | waitforTimeout: 10000,
43 | //
44 | // Default timeout in milliseconds for request
45 | // if browser driver or grid doesn't send response
46 | connectionRetryTimeout: 120000,
47 | //
48 | // Default request retries count
49 | connectionRetryCount: 3,
50 |
51 | framework: 'jasmine',
52 | reporters: ['spec'],
53 | services: ['sauce'],
54 | jasmineOpts: {
55 | defaultTimeoutInterval: 120000,
56 | },
57 | };
58 |
--------------------------------------------------------------------------------
/tado-sec/my-react-app/solution/test/specs/cross.platform.2.spec.js:
--------------------------------------------------------------------------------
1 | describe('Sauce Demo passing', () => {
2 | it('loads', async () => {
3 | // 1. Go to the home page
4 | await browser.url('');
5 | // 2. Create an element variable
6 | const elem = await $('#login-button');
7 | // 3. Verify the element is displayed
8 | await elem.waitForDisplayed();
9 | });
10 | });
11 |
--------------------------------------------------------------------------------
/tado-sec/my-react-app/solution/test/specs/cross.platform.spec.js:
--------------------------------------------------------------------------------
1 | describe('Sauce Demo is flaky', () => {
2 | it('loads', async () => {
3 | // 1. Go to the home page
4 | await browser.url('');
5 | // 2. Create an element variable
6 | const elem = await $('#login-button');
7 | // 3. Verify the element is displayed
8 | await elem.waitForDisplayed();
9 | });
10 | it('fails', async () => {
11 | await browser.url('');
12 | const elem = await $('#log-button');
13 | await elem.waitForDisplayed();
14 | });
15 | });
16 |
--------------------------------------------------------------------------------
/tado-sec/my-react-app/solution/test/specs/localhost.spec.js:
--------------------------------------------------------------------------------
1 | describe('My local react app', () => {
2 | it('renders', async () => {
3 | // navigate to the default url found in wdio.conf.js
4 | await browser.url('/');
5 | // create image variable
6 | const image = await $('[data-testid="main-img"]');
7 | // check if image is displayed
8 | await image.waitForDisplayed();
9 | });
10 | });
11 |
--------------------------------------------------------------------------------
/tado-sec/my-react-app/solution/test/specs/sanity.spec.js:
--------------------------------------------------------------------------------
1 | describe('My environment', () => {
2 | it('works', async () => {
3 | await browser.url('/');
4 | const elem = await $('#login-button');
5 | await elem.waitForDisplayed();
6 | });
7 | });
8 |
--------------------------------------------------------------------------------
/tado-sec/my-react-app/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | height: 40vmin;
7 | pointer-events: none;
8 | }
9 |
10 | @media (prefers-reduced-motion: no-preference) {
11 | .App-logo {
12 | /*animation: App-logo-spin infinite 20s linear;*/
13 | }
14 | }
15 |
16 | .App-header {
17 | background-color: #282c34;
18 | min-height: 100vh;
19 | display: flex;
20 | flex-direction: column;
21 | align-items: center;
22 | justify-content: center;
23 | font-size: calc(10px + 2vmin);
24 | color: white;
25 | }
26 |
27 | .App-link {
28 | color: #61dafb;
29 | }
30 |
31 | @keyframes App-logo-spin {
32 | from {
33 | transform: rotate(0deg);
34 | }
35 | to {
36 | transform: rotate(360deg);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/tado-sec/my-react-app/src/App.js:
--------------------------------------------------------------------------------
1 | import './App.css';
2 | import logo from './mia-me.jpg';
3 |
4 | function App() {
5 | return (
6 |
28 | );
29 | }
30 |
31 | export default App;
32 |
--------------------------------------------------------------------------------
/tado-sec/my-react-app/src/__tests__/App.test.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { render, screen } from '@testing-library/react';
3 | import App from '../App';
4 |
5 | //Using Jest matchers: https://jestjs.io/docs/using-matchers
6 |
7 | test('renders learn react link', () => {
8 | //render our App component in a virtual DOM
9 | render( );
10 | //search for an element by text
11 | const linkElement = screen.getByText('Learn React')
12 | //expect this element to be present in the HTML
13 | expect(linkElement).toBeInTheDocument();
14 | })
--------------------------------------------------------------------------------
/tado-sec/my-react-app/src/__tests__/Exercise.test.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { render, screen } from '@testing-library/react';
3 | import App from '../App';
4 |
5 | //Using Jest matchers: https://jestjs.io/docs/using-matchers
6 | test('link has correct url', () => {
7 | /** Your code below */
8 |
9 | //1. render our App component in a virtual DOM
10 | render( );
11 | //2. search for an element by text
12 | const linkElement = screen.getByText('Learn React')
13 | //3. search for href
14 | expect(linkElement.href).toContain('ultimateqa');
15 |
16 | /** Your code above */
17 | })
18 |
19 | test('link opens in new tab', () => {
20 | /** Your code below */
21 |
22 | //1. render our App component in a virtual DOM
23 | //2. get the link element
24 | //3. expect `linkElement.target` toBe('_blank')
25 |
26 | /** Your code above */
27 | })
28 |
29 |
--------------------------------------------------------------------------------
/tado-sec/my-react-app/src/__tests__/Solution.test.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { render, screen } from '@testing-library/react';
3 | import App from '../App';
4 |
5 | //Using Jest matchers: https://jestjs.io/docs/using-matchers
6 |
7 | test('renders learn react link', () => {
8 | //render our App component in a virtual DOM
9 | render( );
10 | //search for an element by text
11 | const linkElement = screen.getByText('Learn Testing with Mia')
12 |
13 | //Search for element by test id
14 | //const linkElement = screen.getByTestId('learn-link');
15 |
16 | //Using Jest matchers: https://jestjs.io/docs/using-matchers
17 | //expect this element to be present in the HTML
18 | expect(linkElement).toBeInTheDocument();
19 | })
20 |
21 | test('link has correct url', () => {
22 | //render our App component in a virtual DOM
23 | render( );
24 | const linkElement = screen.getByText('Learn Testing with Mia')
25 | expect(linkElement.href).toContain('ultimateqa');
26 | })
27 |
28 | test('link opens in new tab', () => {
29 | //render our App component in a virtual DOM
30 | render( );
31 | const linkElement = screen.getByText('Learn Testing with Mia')
32 | //Link should open a new tab
33 | expect(linkElement.target).toBe('_blank')
34 | })
35 |
--------------------------------------------------------------------------------
/tado-sec/my-react-app/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/tado-sec/my-react-app/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import reportWebVitals from './reportWebVitals';
6 |
7 | ReactDOM.render(
8 |
9 |
10 | ,
11 | document.getElementById('root')
12 | );
13 |
14 | // If you want to start measuring performance in your app, pass a function
15 | // to log results (for example: reportWebVitals(console.log))
16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
17 | reportWebVitals();
18 |
--------------------------------------------------------------------------------
/tado-sec/my-react-app/src/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tado-sec/my-react-app/src/mia-me.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saucelabs-training/automation-best-practices/82c7279cfa1bc88858b0d4be69332d0a29092a9e/tado-sec/my-react-app/src/mia-me.jpg
--------------------------------------------------------------------------------
/tado-sec/my-react-app/src/mia.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saucelabs-training/automation-best-practices/82c7279cfa1bc88858b0d4be69332d0a29092a9e/tado-sec/my-react-app/src/mia.jpg
--------------------------------------------------------------------------------
/tado-sec/my-react-app/src/mia2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saucelabs-training/automation-best-practices/82c7279cfa1bc88858b0d4be69332d0a29092a9e/tado-sec/my-react-app/src/mia2.jpg
--------------------------------------------------------------------------------
/tado-sec/my-react-app/src/reportWebVitals.js:
--------------------------------------------------------------------------------
1 | const reportWebVitals = onPerfEntry => {
2 | if (onPerfEntry && onPerfEntry instanceof Function) {
3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4 | getCLS(onPerfEntry);
5 | getFID(onPerfEntry);
6 | getFCP(onPerfEntry);
7 | getLCP(onPerfEntry);
8 | getTTFB(onPerfEntry);
9 | });
10 | }
11 | };
12 |
13 | export default reportWebVitals;
14 |
--------------------------------------------------------------------------------
/tado-sec/my-react-app/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/tado-sec/my-react-app/test/configs/wdio.localhost.sauce.conf.js:
--------------------------------------------------------------------------------
1 | const sauceOptions = {
2 | build: `WebdriverIO local build-${new Date().getTime()}`,
3 | // tunnelIdentifier: 'sc-proxy-tunnel',
4 | };
5 |
6 | exports.config = {
7 | runner: 'local',
8 | user: process.env.SAUCE_USERNAME,
9 | key: process.env.SAUCE_ACCESS_KEY,
10 | region: 'us',
11 | services: [
12 | [
13 | 'sauce',
14 | {
15 | sauceConnect: true,
16 | },
17 | ],
18 | ],
19 | specs: ['./test/specs/**/localhost.spec.js'],
20 | // Patterns to exclude.
21 | exclude: ['./test/specs/**/sanity.spec.js'],
22 | //
23 | // ============
24 | // Capabilities
25 | // ============
26 | maxInstances: 100,
27 | capabilities: [
28 | //Desktop A 28%: https://www.w3schools.com/browsers/browsers_display.asp
29 | {
30 | browserName: 'chrome',
31 | platformName: 'windows 10',
32 | browserVersion: 'latest',
33 | // needed if you want to run with sauce connect
34 | 'sauce:options': {
35 | ...sauceOptions,
36 | },
37 | },
38 | ],
39 | // Level of logging verbosity: trace | debug | info | warn | error | silent
40 | logLevel: 'error',
41 | // bail (default is 0 - don't bail, run all tests).
42 | bail: 0,
43 | baseUrl: 'http://localhost:3000',
44 | //
45 | // Default timeout for all waitFor* commands.
46 | waitforTimeout: 10000,
47 | //
48 | // Default timeout in milliseconds for request
49 | // if browser driver or grid doesn't send response
50 | connectionRetryTimeout: 120000,
51 | //
52 | // Default request retries count
53 | connectionRetryCount: 3,
54 |
55 | framework: 'jasmine',
56 | reporters: ['spec'],
57 | jasmineOpts: {
58 | defaultTimeoutInterval: 120000,
59 | },
60 | };
61 |
--------------------------------------------------------------------------------
/tado-sec/my-react-app/test/configs/wdio.sanity.sauce.conf.js:
--------------------------------------------------------------------------------
1 | // const sauceOptions = {
2 | // username: process.env.SAUCE_USERNAME,
3 | // accesskey: process.env.SAUCE_ACCESS_KEY,
4 | // };
5 |
6 | exports.config = {
7 | runner: 'local',
8 | user: process.env.SAUCE_USERNAME,
9 | key: process.env.SAUCE_ACCESS_KEY,
10 | region: 'us',
11 | services: [['sauce']],
12 | specs: ['./test/specs/**/sanity.spec.js'],
13 | // Patterns to exclude.
14 | exclude: [
15 | // 'path/to/excluded/files'
16 | ],
17 | //
18 | // ============
19 | // Capabilities
20 | // ============
21 | maxInstances: 100,
22 | capabilities: [
23 | //Desktop A 28%: https://www.w3schools.com/browsers/browsers_display.asp
24 | {
25 | browserName: 'chrome',
26 | platformName: 'windows 10',
27 | browserVersion: 'latest',
28 | // 'sauce:options': {
29 | // ...sauceOptions,
30 | // },
31 | },
32 | ],
33 | // Level of logging verbosity: trace | debug | info | warn | error | silent
34 | logLevel: 'error',
35 | // bail (default is 0 - don't bail, run all tests).
36 | bail: 0,
37 | baseUrl: 'https://www.saucedemo.com',
38 | //
39 | // Default timeout for all waitFor* commands.
40 | waitforTimeout: 10000,
41 | //
42 | // Default timeout in milliseconds for request
43 | // if browser driver or grid doesn't send response
44 | connectionRetryTimeout: 120000,
45 | //
46 | // Default request retries count
47 | connectionRetryCount: 3,
48 |
49 | framework: 'jasmine',
50 | reporters: ['spec'],
51 | jasmineOpts: {
52 | defaultTimeoutInterval: 120000,
53 | },
54 | };
55 |
--------------------------------------------------------------------------------
/tado-sec/my-react-app/test/configs/wdio.sauce.conf.js:
--------------------------------------------------------------------------------
1 | const sauceOptions = {
2 | username: process.env.SAUCE_USERNAME,
3 | accesskey: process.env.SAUCE_ACCESS_KEY,
4 | };
5 |
6 | exports.config = {
7 | runner: 'local',
8 | user: process.env.SAUCE_USERNAME,
9 | key: process.env.SAUCE_ACCESS_KEY,
10 | region: 'us',
11 | services: [
12 | [
13 | 'sauce',
14 | {
15 | sauceConnect: true,
16 | },
17 | ],
18 | ],
19 | specs: ['./test/specs/**/**.spec.js'],
20 | // Patterns to exclude.
21 | exclude: ['./test/specs/**/sanity.spec.js'],
22 | //
23 | // ============
24 | // Capabilities
25 | // ============
26 | maxInstances: 100,
27 | capabilities: [
28 | //Desktop A 28%: https://www.w3schools.com/browsers/browsers_display.asp
29 | {
30 | browserName: 'chrome',
31 | platformName: 'windows 10',
32 | browserVersion: 'latest',
33 | 'sauce:options': {
34 | ...sauceOptions,
35 | },
36 | },
37 | ],
38 | // Level of logging verbosity: trace | debug | info | warn | error | silent
39 | logLevel: 'error',
40 | // bail (default is 0 - don't bail, run all tests).
41 | bail: 0,
42 | baseUrl: 'http://localhost:3000',
43 | //
44 | // Default timeout for all waitFor* commands.
45 | waitforTimeout: 10000,
46 | //
47 | // Default timeout in milliseconds for request
48 | // if browser driver or grid doesn't send response
49 | connectionRetryTimeout: 120000,
50 | //
51 | // Default request retries count
52 | connectionRetryCount: 3,
53 |
54 | framework: 'jasmine',
55 | reporters: ['spec'],
56 | jasmineOpts: {
57 | defaultTimeoutInterval: 120000,
58 | },
59 | };
60 |
--------------------------------------------------------------------------------
/tado-sec/my-react-app/test/specs/localhost.spec.js:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/tado-sec/my-react-app/test/specs/login.spec.js:
--------------------------------------------------------------------------------
1 | describe('Home page', () => {
2 | it('renders', async () => {
3 | await browser.url('/');
4 | const elem = await $('[data-testid="main-img"]');
5 | await elem.waitForDisplayed();
6 | });
7 | it('works with valid user', async () => {
8 | await browser.url('https://www.saucedemo.com');
9 | const elem = await $('#login-button');
10 | await elem.waitForDisplayed();
11 | });
12 | });
13 |
--------------------------------------------------------------------------------
/tado-sec/my-react-app/test/specs/sanity.spec.js:
--------------------------------------------------------------------------------
1 | describe('My environment', () => {
2 | it('works', async () => {
3 | await browser.url('/');
4 | const elem = await $('#login-button');
5 | await elem.waitForDisplayed();
6 | });
7 | });
8 |
--------------------------------------------------------------------------------
/testing-for-charity/README.md:
--------------------------------------------------------------------------------
1 | # automation best practices workshop
2 |
3 | 
4 |
5 | [#testing4good](https://twitter.com/hashtag/Testing4Good)
6 |
7 | In this automation best practices workshop you will learn the latest and greatest tools and techniques to drastically improve your testing!
8 |
9 | We will focus on a holistic approach of testing front-end and back-end, web and APIs, functional testing, component testing, and many other things in between 😁
10 |
11 | This workshop serves 2 purposes:
12 |
13 | 1. For me to give back to the testing world and help us all upskill 🚀
14 | 2. For us all to help a greater cause than ourselves 🌍
15 |
16 | ### [About Black Girls CODE](https://www.blackgirlscode.com/about-us/)
17 |
18 | We build pathways for young women of color to embrace the current tech marketplace as builders and creators by introducing them to skills in computer programming and technology.
19 |
20 | Radical action is needed if we are to close the opportunity gap for Black women and girls. We lead a global movement to establish equal representation in the tech sector. Black Girls CODE is devoted to showing the world that Black girls can code and do so much more. Together, we are creating stronger economies and more equitable societies—ultimately realizing the true potential of democracy through diversity and inclusion.
21 |
22 | 👇👇👇
23 |
24 | [Please donate whatever you feel appropriate.](https://www.gofundme.com/f/testing-for-charity) 100% of the donations go to the cause.
25 |
26 | ## 🧠You will learn
27 |
28 | ✅how to test a web application using different types of tests
29 |
30 | ✅functional ui tests with Cypress
31 |
32 | ✅visual e2e tests
33 |
34 | ✅visual cross-browser tests
35 |
36 | ✅CICD with Github Actions
37 |
38 | ✅component testing
39 |
40 | ## 🔧Technologies you will use
41 |
42 | 1. ReactJS
43 | 2. Cypress
44 | 3. WebdriverIO
45 | 4. Screener.io
46 | 5. Sauce Labs
47 | 6. Github Actions
48 | 7. React Testing Library
49 |
50 | ## Table Of Contents
51 |
52 | * [E2E UI testing w/ Cypress](./my-react-app/docs/E2E-TESTS.md)
53 | * [CICD](./my-react-app/docs/CICD.md)
54 | * [Visual e2e tesing](./my-react-app/docs/VISUAL.md)
55 | * [Component testing](./my-react-app/docs/COMPONENT-TESTS.md)
56 | * [Conclusions](./my-react-app/docs/CONCLUSIONS.md)
57 |
58 | ## Key
59 |
60 | 💡 this is a tip
61 |
62 | 🏋️♀️ this is an exercise for you to do
63 |
64 | ❓ this is a question for us to think and talk about. Try not to scroll beyond this question before we discuss
65 |
66 | ## Your Instructor: Nikolay Advolodkin
67 |
68 |
69 |
70 | - 🔭 I’m the founder of [Ultimate QA](https://ultimateqa.com/)
71 | - 🏢 I’m a Sr Solutions Architect at Sauce Labs
72 | - 🌱 I’m currently working on [Sauce Bindings](https://github.com/saucelabs/sauce_bindings)
73 | - 💬 Ask me about environmentalism, veganism, test automation, and fitness
74 | - 😄 Pronouns: he/him
75 | - ⚡ Fun fact: I'm a vegan that's super pasionate about saving the planet, saving animals, and helping underpriveleged communities
76 | - 📫 Follow me for testing and dev training
77 | - [JS Testing Newsletter](https://ultimateqa.ck.page/js-testing-tips)
78 | - [Youtube](https://youtube.com/ultimateqa)
79 | - [LinkedIn](https://www.linkedin.com/in/nikolayadvolodkin/)
80 | - [Twitter](https://twitter.com/Nikolay_A00)
81 |
82 | ## Your TA: Josh Grant
83 |
84 |
85 |
86 | Josh Grant is a test automation professional who has worked in this space for nearly a decade. In that time he has worked on automation at all levels, in a variety of languages, frameworks and organizations.
87 | Currently he is a solution architect at Sauce Labs, helping enterprise teams succeed with their test automation. He’s based in Toronto, Ontario.
88 |
89 | ## ⚙️ Setup
90 |
91 | ### 1. Install Node 14 LTS
92 | 1. Use NVM for this installation by [following instructions](https://github.com/nvm-sh/nvm#install--update-script)
93 | * It should be just a single command to run in our terminal
94 | * **!Don't forget to restart your terminal!**
95 | 2. After installation, confirm install with `nvm --version`
96 | 3. Intall Node 14 with `nvm install 14`
97 |
98 |
99 |
100 |
101 | Click here to see an example output.
102 |
103 |
104 | Downloading and installing node v14.16.1...
105 | Downloading https://nodejs.org/dist/v14.16.1/node-v14.16.1-darwin-x64.tar.xz...
106 | ######################################################################### 100.0%
107 | Computing checksum with shasum -a 256
108 | Checksums matched!
109 | Now using node v14.16.1 (npm v6.14.12)
110 | Creating default alias: default -> 14 (-> v14.16.1)
111 |
112 |
113 |
114 |
115 | * Confirm node installation with `node --version` and seeing `v14.16.1` or similar
116 | * Confirm NVM is set to 14 for default by running the following commands:
117 |
118 | ```bash
119 | nvm list #will show all versions
120 | nvm use 14 #to use 14
121 | nvm alias default 14.16.x #to set it to the default
122 | ```
123 |
124 |
125 |
126 | ### 2.Clone and fork the repo
127 | 1. Sign up for a free [Github account](https://github.com/)
128 | 2. Fork this respository
129 | * Make sure you are logged into Github
130 | * click the Fork in the upper right of the Github.
131 | 3. Clone your fork of the repository to your machine. Must have [Git installed](https://git-scm.com/downloads)
132 |
133 | ```bash
134 | git clone URL_OF_YOUR_FORK
135 | ```
136 | 4. **Navigate to the directory of where you cloned your repo**
137 |
138 | `cd YOUR_FORK_DIR/automation-best-practices/testing-for-charity`
139 |
140 | ### 3.Install the app
141 |
142 | ```bash
143 | cd testing-for-charity/my-react-app
144 | npm install && npm start
145 | ```
146 |
147 |
148 |
149 | Click here to see an example output.
150 |
151 |
152 |
153 |
154 | Compiled successfully!
155 |
156 | You can now view my-react-app in the browser.
157 |
158 | Local: http://localhost:3000
159 | On Your Network: http://172.20.10.2:3000
160 |
161 | Note that the development build is not optimized.
162 | To create a production build, use npm run build.
163 |
164 |
165 |
166 |
167 |
168 |
169 | **Don't worry about fixing any warnings that may appear during `npm install`**
170 |
171 | Once the app is running, kill the server by executing `Ctrl + C` in command line. We don't need the app running right now.
172 |
173 | ### 4.Have an IDE installed that can handle NodeJS/JS (We will use [VSCode](https://code.visualstudio.com/Download))
174 |
175 | #### ✅👏Congratulations, your environment is ready!
176 |
177 | ### 5.Sign up for a free [Sauce account](https://saucelabs.com/sign-up)
178 |
179 | Get your [Screener account](https://saucelabs.com/demo-request-vt)
180 |
181 | ### 6.Set Your Sauce Labs Credentials
182 |
183 | * [MacOS/Linux](https://docs.saucelabs.com/basics/environment-variables/#setting-up-environment-variables-on-macos-and-linux-systems)
184 | * [Windows](https://docs.saucelabs.com/basics/environment-variables/#setting-up-environment-variables-on-windows-systems)
185 |
186 |
187 |
188 |
189 |
190 |
191 |
--------------------------------------------------------------------------------
/testing-for-charity/my-react-app/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/testing-for-charity/my-react-app/cypress.json:
--------------------------------------------------------------------------------
1 | {
2 | "baseUrl": "http://localhost:3000"
3 | }
4 |
--------------------------------------------------------------------------------
/testing-for-charity/my-react-app/cypress/fixtures/example.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Using fixtures to represent data",
3 | "email": "hello@cypress.io",
4 | "body": "Fixtures are a great way to mock data for responses to routes"
5 | }
--------------------------------------------------------------------------------
/testing-for-charity/my-react-app/cypress/integration/exercise.spec.js:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | describe('Exercise', () => {
4 | it('loads', ()=> {
5 | /** Your code below */
6 |
7 | //1. Use cy.visit('/') to go to the app url
8 | //2. Use cy.get('.App-link').should('be.visible') to assert valid state
9 | /** Your code above */
10 | })
11 |
12 | it('link goes to ultimateqa', ()=> {
13 | /** Your code below */
14 |
15 | // 1. Use cy.visit('/') to go to the app url
16 | // 2. cy.get('').should('have.attr', 'href').and('include', 'ultimateqa.com')
17 |
18 | /** Your code above */
19 | })
20 |
21 | it('should open link in new tab',()=> {
22 | /** Your code below */
23 |
24 | // 1. Cmon, you know how to open the url now :)
25 | // 2. cy.get('').should('have.attr', 'WHAT').and('include', 'WHAT VALUE')
26 |
27 | /** Your code above */
28 | })
29 | })
30 |
--------------------------------------------------------------------------------
/testing-for-charity/my-react-app/cypress/integration/solution.spec.js:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | describe('Solution', () => {
4 | it('loads', ()=> {
5 | cy.visit('/')
6 | cy.get('.App-link').should('be.visible')
7 | })
8 |
9 | it('link goes to ultimateqa', ()=> {
10 | cy.visit('/')
11 | cy.get('.App-link').should('have.attr', 'href').and('include', 'ultimateqa.com')
12 | })
13 |
14 | it('should open link in new tab',()=> {
15 | cy.visit('/');
16 | cy.get('.App-link').should('have.attr','target').and('include', '_blank');
17 | })
18 | })
19 |
--------------------------------------------------------------------------------
/testing-for-charity/my-react-app/cypress/plugins/index.js:
--------------------------------------------------------------------------------
1 | ///
2 | // ***********************************************************
3 | // This example plugins/index.js can be used to load plugins
4 | //
5 | // You can change the location of this file or turn off loading
6 | // the plugins file with the 'pluginsFile' configuration option.
7 | //
8 | // You can read more here:
9 | // https://on.cypress.io/plugins-guide
10 | // ***********************************************************
11 |
12 | // This function is called when a project is opened or re-opened (e.g. due to
13 | // the project's config changing)
14 |
15 | /**
16 | * @type {Cypress.PluginConfig}
17 | */
18 | module.exports = (on, config) => {
19 | // `on` is used to hook into various events Cypress emits
20 | // `config` is the resolved Cypress config
21 | }
22 |
--------------------------------------------------------------------------------
/testing-for-charity/my-react-app/cypress/support/commands.js:
--------------------------------------------------------------------------------
1 | // ***********************************************
2 | // This example commands.js shows you how to
3 | // create various custom commands and overwrite
4 | // existing commands.
5 | //
6 | // For more comprehensive examples of custom
7 | // commands please read more here:
8 | // https://on.cypress.io/custom-commands
9 | // ***********************************************
10 | //
11 | //
12 | // -- This is a parent command --
13 | // Cypress.Commands.add("login", (email, password) => { ... })
14 | //
15 | //
16 | // -- This is a child command --
17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
18 | //
19 | //
20 | // -- This is a dual command --
21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
22 | //
23 | //
24 | // -- This will overwrite an existing command --
25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
26 |
--------------------------------------------------------------------------------
/testing-for-charity/my-react-app/cypress/support/index.js:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example support/index.js is processed and
3 | // loaded automatically before your test files.
4 | //
5 | // This is a great place to put global configuration and
6 | // behavior that modifies Cypress.
7 | //
8 | // You can change the location of this file or turn off
9 | // automatically serving support files with the
10 | // 'supportFile' configuration option.
11 | //
12 | // You can read more here:
13 | // https://on.cypress.io/configuration
14 | // ***********************************************************
15 |
16 | // Import commands.js using ES2015 syntax:
17 | import './commands'
18 |
19 | // Alternatively you can use CommonJS syntax:
20 | // require('./commands')
21 |
--------------------------------------------------------------------------------
/testing-for-charity/my-react-app/docs/CICD.md:
--------------------------------------------------------------------------------
1 | # CICD
2 |
3 | ## 🏋️♀️Let's add this code to our CI system.
4 |
5 | 1. Create a `charity.ci.yml` file in this folder structure `.github/workflows/` in the root of your directory
6 | 2. Paste in the following configuration
7 |
8 | ```yml
9 | name: Testing for Charity
10 |
11 | on:
12 | push:
13 | branches: [ main ]
14 | pull_request:
15 | branches: [ main ]
16 |
17 | jobs:
18 | build:
19 |
20 | runs-on: ubuntu-latest
21 |
22 | strategy:
23 | matrix:
24 | node-version: [14.x]
25 |
26 | steps:
27 | - uses: actions/checkout@v2
28 | - name: Use Node.js ${{ matrix.node-version }}
29 | uses: actions/setup-node@v1
30 | with:
31 | node-version: ${{ matrix.node-version }}
32 | - name: Install dependencies 📦
33 | #Using npm ci is generally faster than running npm install
34 | run: |
35 | cd testing-for-charity/my-react-app
36 | npm ci
37 | - name: Build the app 🏗
38 | run: |
39 | cd testing-for-charity/my-react-app
40 | npm run build
41 | # If we had more time, at this point we can actually deploy our app
42 | # to a staging server and then run functional tests
43 | - name: Start the app 📤
44 | run: |
45 | cd testing-for-charity/my-react-app
46 | npm start &
47 | npx wait-on --timeout 60000
48 | - name: Run functional UI tests 🖥
49 | run: |
50 | cd testing-for-charity/my-react-app
51 | npm run cy:ci
52 | ```
53 | 3. Add New repository secrets for the repo
54 |
55 | 
56 |
57 | 4. `git push` and watch it run
58 |
59 | ## 🧪Current Test Coverage
60 |
61 | [Look here](TEST-COVERAGE.md)
62 |
63 | ## 📝Summary
64 |
65 | ✅We can use Github Workflows for free and easy continuous integration pipelines
66 |
67 | ## ⏭️[Let's add visual tests](./VISUAL.md)
68 |
--------------------------------------------------------------------------------
/testing-for-charity/my-react-app/docs/COMPONENT-TESTS.md:
--------------------------------------------------------------------------------
1 | # Component Testing
2 |
3 | ## 🧠You will learn
4 |
5 | ✅How to write a component test
6 |
7 | ✅How to replace e2e tests with component tests
8 |
9 | ## ⚙️ Setup
10 |
11 | Make sure that application is up and running
12 |
13 | ## 🧪Our Testing Strategy
14 |
15 | [Look here](TEST-COVERAGE.md)
16 |
17 | ---
18 |
19 | ### ❓What are the disadvantages of functional UI tests?
20 |
21 | ---
22 |
23 | 1. Need a browser
24 | 2. Need a server
25 | 3. Need to deal with network issues
26 | 4. Test will be slower (100X to 1000X slower)
27 | 5. Need an extra dependency (Cypress)
28 | 6. Need to learn extra dependency API
29 |
30 | ---
31 |
32 | ### ❓Can we test the same thing more efficiently❓
33 |
34 | ---
35 |
36 | ## Component tests
37 |
38 | There are a few ways to test React components. Broadly, they divide into two categories:
39 |
40 | * Rendering component trees in a simplified test environment and asserting on their output.
41 | * Running a complete app in a realistic browser environment (also known as “end-to-end” tests)
42 |
43 | [ReactJS.org](https://reactjs.org/docs/testing.html)
44 |
45 | ### What is a component test?
46 |
47 | 
48 |
49 | [Yoni Goldberg](https://github.com/nadvolod/component-tests-workshop/blob/main/graphics/component-diagram.jpg)
50 |
51 | #### Advantages of component tests
52 |
53 | ❌ browser
54 |
55 | ❌ server
56 |
57 | ❌ network issues
58 |
59 | ✅Tests run in ms instead of sec
60 |
61 | ❌ extra dependency (Cypress)
62 |
63 | ❌ extra dependency API
64 |
65 |
66 | ### 🏋️♀️ Code the component test
67 |
68 | Our app was created using [create-react-app](https://create-react-app.dev/).
69 | "Set up a modern web app by running one command"
70 |
71 | With this method, we automatically get a few tools for testing:
72 | * @testing-library/react
73 | * jest
74 | * And we get an automatic component test in `my-react-app/src/App.test.js`
75 |
76 | ```js
77 | //Exercise.test.js
78 | test('renders learn react link', () => {
79 | render( );
80 | const linkElement = screen.getByText(/learn react/i);
81 | expect(linkElement).toBeInTheDocument();
82 | });
83 | ```
84 | [React Testing Library](https://testing-library.com/docs/react-testing-library/intro/) is a very light-weight solution for testing React components. It provides light utility functions on top of react-dom and react-dom/test-utils, in a way that encourages better testing practices.
85 |
86 | [Jest](https://jestjs.io/) is a JavaScript testing framework designed to ensure correctness of any JavaScript codebase. It allows you to write tests with an approachable, familiar and feature-rich API that gives you results quickly.
87 |
88 | ### 🏋️♀️Run the component test
89 |
90 | 1. Stop all servers
91 | 2. `cd testing-for-charity/my-react-app/ && npm run test`
92 | 3. The tests should pass, even though our app isn't running.
93 |
94 | 💡 'p' to filter tests down to a specific file
95 |
96 | 💡 'o' to run tests only in the changed files
97 |
98 | ### Testing links with component tests
99 |
100 | 💡Remember this Cypress e2e link test? We can test the same thing without a browser or a server
101 |
102 | ```js
103 | it('link goes to ultimateqa', ()=> {
104 | cy.visit('/')
105 | cy.get('.App-link').should('have.attr', 'href').and('include', 'ultimateqa.com')
106 | })
107 | ```
108 |
109 | 1. Run `npm test` in `testing-for-charity/my-react-app` if it's not running already
110 | 2. Go to `src/__tests__/Exercise.test.js` and follow the instructions for `test('link has correct url'`
111 | 3. Save and the test runs automatically
112 |
113 | ### How to add a test id to our app
114 |
115 | - A `className` can change
116 | - Link text can also change
117 | - A `data-` attribute is uniquely created for testing purposes and has no impact on the functionality of the application
118 |
119 |
120 | #### 🏋️♀️Add a `data-testid` attribute to our element
121 | 1. Open `my-react-app/src/App.js`
122 | 2. In the `` of the App component add `data-testid="learn-link"` property
123 |
124 | ```html
125 |
132 | ```
133 |
134 | 3. Open `src/__tests__/Exercise.test.js`
135 | 4. Update the test to use `getByTestId()` instead `const linkElement = screen.getByTestId('learn-link');`
136 | 5. Save and the test will automatically rerun
137 |
138 | ### 🏋️♀️Add a test to validate that link opens in new tab
139 |
140 | 1. Open `src/__tests__/Exercise.test.js`
141 | 2. Follow instructions in `getByTestId()` instead `const linkElement = screen.getByTestId('learn-link');`
142 |
143 | ---
144 |
145 | #### ❓What does the Cypress test validate that the component test does not❓
146 |
147 | ---
148 |
149 | ## 🧪Our Test Coverage
150 |
151 | [Look here](./TEST-COVERAGE.md)
152 |
153 | ## 📝Summary
154 |
155 | ✅An e2e test (Cypress) tests the connection from the front-end to the back-end
156 |
157 | ✅A majority of the functionality of a modern JavaScript web app (React, Vue, Angular) can be tested with component tests in a virtual DOM. We did this with component tests
158 |
159 | ## CICD with component tests
160 |
161 | ### 🏋️♀️Add component testing to our CI pipeline
162 |
163 | #### ❓Where in the `yml` would you add this snippet of code and why?
164 |
165 | ```yml
166 | - name: Run component tests 🔸
167 | run: |
168 | cd my-react-app
169 | npm run test
170 | ```
171 |
172 | ## ⏭️[Conclusions](./CONCLUSIONS.md)
173 |
--------------------------------------------------------------------------------
/testing-for-charity/my-react-app/docs/CONCLUSIONS.md:
--------------------------------------------------------------------------------
1 | # conclusions
2 |
3 | ## before you go!
4 |
5 | 
6 |
7 | 1. [Please donate whatever you feel appropriate.](https://www.gofundme.com/f/testing-for-charity) 100% of the donations go to the cause.
8 | 2. 📫 Follow me as I will do many more Testing for Good events
9 |
10 | - [JS Testing Newsletter](https://ultimateqa.ck.page/js-testing-tips)
11 | - [Youtube](https://youtube.com/ultimateqa)
12 | - [LinkedIn](https://www.linkedin.com/in/nikolayadvolodkin/)
13 | - [Twitter](https://twitter.com/Nikolay_A00)
14 |
15 | 3. Please give me [anonymous feedback on the workshop](https://docs.google.com/forms/d/e/1FAIpQLSf038xralb1Lo5ZCj8-b2CCOktD7WpfspKCvvpYcnq5wPRidQ/viewform?usp=sf_link)
16 |
17 | ## Thanks so much for your time and generosity 🙌👏
18 |
19 | 
20 |
21 | This workshop serves 2 purposes:
22 |
23 | 1. For me to give back to the testing world and help us all upskill 🚀
24 | 2. For us all to help a greater cause than ourselves 🌍
25 |
26 | ### [About Black Girls CODE](https://www.blackgirlscode.com/about-us/)
27 |
28 | We build pathways for young women of color to embrace the current tech marketplace as builders and creators by introducing them to skills in computer programming and technology.
29 |
30 | Radical action is needed if we are to close the opportunity gap for Black women and girls. We lead a global movement to establish equal representation in the tech sector. Black Girls CODE is devoted to showing the world that Black girls can code and do so much more. Together, we are creating stronger economies and more equitable societies—ultimately realizing the true potential of democracy through diversity and inclusion.
31 |
32 | 👇👇👇
33 |
34 | [Please donate whatever you feel appropriate.](https://www.gofundme.com/f/testing-for-charity) 100% of the donations go to the cause.
35 |
--------------------------------------------------------------------------------
/testing-for-charity/my-react-app/docs/E2E-TESTS.md:
--------------------------------------------------------------------------------
1 | # E2E Browser Tests
2 |
3 | ## 🧠You will learn
4 |
5 | ✅How to test a web application with functional browser tests (aka E2E tests)
6 |
7 |
8 | ## ⚙️ Setup
9 |
10 | 1. Stop all servers from previous session (`Ctrl + C` everything)
11 | 2. cd `testing-for-charity/my-react-app`
12 | 3. `npm install && npm start`
13 |
14 | Open application at http://localhost:3000/
15 |
16 | ## 🧪Our Testing Strategy
17 |
18 | [Look here](TEST-COVERAGE.md)
19 |
20 | ## What is the most basic test that we can write for our application?
21 |
22 | * How about a functional browser test sure that our app renders?
23 |
24 | ## Cypress Overview
25 |
26 | "Fast, easy and reliable testing for anything that runs in a browser."([Cypress.io](https://www.cypress.io/))
27 |
28 | ```bash
29 | cd testing-for-charity/my-react-app
30 | npx cypress open
31 | ```
32 |
33 | 💡 Tests live in `cypress/integration` folder
34 |
35 | ### 🏋️♀️Write a Cypress test to make sure that our app opens.
36 |
37 | 1. In your IDE open `cypress/integration/exercise.spec.js`
38 | 2. Follow instructions to implement `it('loads')` test
39 |
40 | ---
41 |
42 | ### ❓What does this test validate?
43 |
44 | ---
45 |
46 | ## 🧪Current Test Coverage
47 |
48 | [Look here](TEST-COVERAGE.md)
49 |
50 | ---
51 |
52 | ### ❓How do we ensure that the link is correct?
53 |
54 | ---
55 |
56 | ## 🔗Testing links (the right way)
57 |
58 | Here's an e2e test to validate that a link works
59 |
60 | ```js
61 | it('should click link',()=>{
62 | cy.visit('/');
63 | cy.get('.App-link').click().url().should('contain','ultimateqa.com');
64 | })
65 | ```
66 | ---
67 |
68 | ### ❓What is the problem with this test❓
69 |
70 | ---
71 |
72 | 1. We should never need to test that a link is clickable, this is the browser's native behavior
73 | 2. We should never need to test that a link opens a new tab
74 |
75 | ### 🏋️♀️Write a test to ensure that the link will go to the correct location
76 |
77 | 1. In your IDE open `cypress/integration/exercise.spec.js`
78 | 2. Follow instructions to implement `it('link goes to ultimateqa')` test
79 |
80 |
81 | ---
82 |
83 | ❓What is the exact validation of this test❓
84 |
85 | ---
86 |
87 | ## 🧪Current Test Coverage
88 |
89 | [Look here](TEST-COVERAGE.md)
90 |
91 | ## How to test that the link opens in a new tab?
92 |
93 | 👀 Working with 'target' attribute
94 |
95 | 🏋️♀️Write a functional ui test to validate that the link opens in a new tab
96 |
97 | * Follow instructions in this test `it('should open link in new tab')`
98 |
99 | ## 🧪Current Test Coverage
100 |
101 | [Look here](TEST-COVERAGE.md)
102 |
103 | ---
104 |
105 | ### ❓What are the disadvantages of functional UI tests?
106 |
107 | ---
108 |
109 | 1. Need a browser
110 | 2. Need a server
111 | 3. Need to deal with network issues
112 | 4. Test will be slower
113 | 5. Need an extra dependency (Cypress)
114 | 6. Need to learn extra dependency API
115 |
116 | ---
117 |
118 | ### ❓Can we test the same thing more efficiently❓
119 |
120 | ---
121 |
122 | ## 📝Summary
123 |
124 | ✅E2E UI testing with Cypress allows us to do functional testing of the web app
125 |
126 | ✅However, it's inneficient and there are better alternatives
127 |
128 | ## ⏭️Wouldn't it be great to have this tested automatically through CI?
129 |
130 | [Let's setup up CI](./CICD.md)
--------------------------------------------------------------------------------
/testing-for-charity/my-react-app/docs/FRONT-END-PERF.md:
--------------------------------------------------------------------------------
1 | ## Front-end performance
2 |
3 | * Use the task tracker application that we build in React JS crash course
4 | * Get the performance metrics by using in the About and Footer components
5 | * Then change those to a Link component
6 | * Then recapture the front-end perf metrics
7 | * The expected result is that with the instant DOM refresh, the latter will be faster
--------------------------------------------------------------------------------
/testing-for-charity/my-react-app/docs/GETTING-STARTED-REACT.md:
--------------------------------------------------------------------------------
1 | # Getting Started with Create React App
2 |
3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
4 |
5 | ## Available Scripts
6 |
7 | In the project directory, you can run:
8 |
9 | ### `npm start`
10 |
11 | Runs the app in the development mode.\
12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
13 |
14 | The page will reload if you make edits.\
15 | You will also see any lint errors in the console.
16 |
17 | ### `npm test`
18 |
19 | Launches the test runner in the interactive watch mode.\
20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
21 |
22 | ### `npm run build`
23 |
24 | Builds the app for production to the `build` folder.\
25 | It correctly bundles React in production mode and optimizes the build for the best performance.
26 |
27 | The build is minified and the filenames include the hashes.\
28 | Your app is ready to be deployed!
29 |
30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
31 |
32 | ### `npm run eject`
33 |
34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!**
35 |
36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
37 |
38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
39 |
40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
41 |
42 | ## Learn More
43 |
44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
45 |
46 | To learn React, check out the [React documentation](https://reactjs.org/).
47 |
48 | ### Code Splitting
49 |
50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
51 |
52 | ### Analyzing the Bundle Size
53 |
54 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
55 |
56 | ### Making a Progressive Web App
57 |
58 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
59 |
60 | ### Advanced Configuration
61 |
62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
63 |
64 | ### Deployment
65 |
66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
67 |
68 | ### `npm run build` fails to minify
69 |
70 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
71 |
--------------------------------------------------------------------------------
/testing-for-charity/my-react-app/docs/TEST-COVERAGE.md:
--------------------------------------------------------------------------------
1 | ## 🧪Our Testing Strategy
2 |
3 | | Expected Behavior | Tested? | Test Type | Technologies |
4 | |---|---|---|---|
5 | | Application renders | ✅ | ui/e2e/functional | Cypress |
6 | | Link goes to correct location | ✅ | ui/e2e/functional | Cypress |
7 | | Link opens in new tab | ✅ | ui/e2e/functional | Cypress |
8 | | App looks as expected on Chrome + Safari on most popular resolution | ✅ | visual | WebdriverIO,Screener |
9 | | App is accessibility friendly | 🙅♂️ | | |
10 | | Front-end performance is at least a B | 🙅♂️ | | |
11 | | App is secure | 🙅♂️ | | |
12 | | Multiple other testing types... | 🙅♂️ | | |
13 |
--------------------------------------------------------------------------------
/testing-for-charity/my-react-app/docs/VISUAL-COMPONENT.md:
--------------------------------------------------------------------------------
1 | # Visual Component Testing
2 |
3 | ## 🧠You will learn
4 |
5 |
6 | ## 🧪Current Test Coverage
7 |
8 | | Expected Behavior | Tested? | Test Type | Technologies |
9 | |---|---|---|---|
10 | | Application renders | ✅ | Functional UI | Cypress |
11 | | Learn React link goes to correct location | ✅ | Functional UI | Cypress |
12 | | Learn React link opens in new tab | ✅ | Functional UI | Cypress |
13 | | App looks as expected on Chrome + Safari on most popular resolution | 🙅♂️ | | |
14 | | App looks as expected on iPhone 12, 12 Pro Max | 🙅♂️ | | |
15 |
16 | ---
17 |
18 | ## ❓How do we check to make sure that the app looks as expected on web and mobile?
19 |
20 | ---
21 |
22 | ### Unit tests don't have eyeballs
23 |
24 | A unit test isolates a module and then verifies its behavior. It supplies inputs (props, state, etc.) and compares the output to an expected result. Unit tests are desirable because testing modules in isolation makes it easier to cover edge cases and pinpoint the source of failures.
25 |
26 | The core issue is that much of a UI's inherent complexity is visual — the specifics of how generated HTML and CSS render on the user's screen.
27 |
28 | [Storybook visual testing](https://storybook.js.org/tutorials/visual-testing-handbook/react/en/introduction/)
29 |
30 | ## What about snapshot tests?
31 |
32 | Snapshot tests provide an alternate approach to verifying UI appearance. They render the component then capture the generated DOM as a "baseline".
33 |
34 | In practice, DOM snapshots are awkward because it's tricky to determine how a UI renders by evaluating an HTML blob.
35 |
36 | Snapshot tests suffer from the same brittleness as other automated UI tests. Any changes to the internal workings of a component require the test to be updated, regardless of whether the component's rendered output changed.
37 |
38 | ### Visual testing is made for UIs
39 |
40 | Visual tests are designed to catch changes in UI appearance. You use a component explorer like Storybook to isolate UI components, mock their variations, and save the supported test cases as "stories".
41 |
42 | ## Visual testing
43 |
44 | Visual testing workflow
45 | In practice, visual testing uses Storybook to “visually” test a component across a set of defined test states. Visual tests share the same setup, execute, and teardown steps as any other type of testing, but the verification step falls to the user.
46 |
47 | ```
48 | test do
49 | setup
50 | execute 👈 Storybook renders stories
51 | verify 👈 you look at stories
52 | teardown
53 | end
54 | ```
55 |
56 | ## How to write visual test cases
57 |
58 | 1. `npx sb init`
59 | 2. `npm run storybook`
--------------------------------------------------------------------------------
/testing-for-charity/my-react-app/docs/VISUAL.md:
--------------------------------------------------------------------------------
1 | # Visual E2E Testing
2 |
3 | ## 🧠You will learn
4 |
5 | ✅What is visual E2E testing?
6 |
7 | ✅How to implement visual e2e testing for a web app
8 |
9 | ## 🧪Current Test Coverage
10 |
11 | [Look here](TEST-COVERAGE.md)
12 |
13 | ---
14 |
15 | ## ❓How do we check to make sure that the app looks as expected on web and mobile?
16 |
17 | ---
18 |
19 |
20 |
21 |
22 |
23 | ### Advantages of visual tests
24 |
25 | ✅ Easier than e2e tests
26 |
27 | ✅ Validate an entire page in a single LOC
28 |
29 | ✅ Check how an app looks in different Browsers/Resolutions
30 |
31 | ✅ More stable than e2e tests
32 |
33 | ✅ Can help reduce the size of e2e suite
34 |
35 | [Deeper discussion of visual testing](https://docs.google.com/presentation/d/13jYXXoKb36aFt1HLnNnAmsPqw9yaFhVrB4iFH_5_WkI/edit#slide=id.gcc181d5a54_0_284)
36 |
37 | ## Our tools
38 |
39 | ### [WebdriverIO](https://webdriver.io/)
40 | Next-gen browser and mobile automation test framework for Node.js
41 |
42 | ### [Screener](https://screener.io/)
43 | Automatically detect visual regressions across your UI
44 |
45 | ## Set up a visual test
46 |
47 | follow along
48 |
49 | 1. Create a new file `my-react-app/test/specs/exercise.spec.js`
50 | 2. Paste the following code
51 |
52 | ```javascript
53 |
54 | describe('My app', () => {
55 | it('should look correct', async () => {
56 | await browser.url('');
57 | await browser.execute('/*@visual.init*/', 'My React App');
58 | await browser.execute('/*@visual.snapshot*/', 'Home Page');
59 |
60 | const result = await browser.execute('/*@visual.end*/');
61 | expect(result.message).toBeNull();
62 | });
63 | });
64 |
65 | ```
66 | 3. `cd testing-for-charity/my-react-app`
67 | 4. `npm run test:visual`
68 | 5. View your results in Screener.io
69 |
70 | [Let's fill out the Test coverage](./TEST-COVERAGE.md)
71 |
72 | ---
73 |
74 | ### 🏋️♀️❓ Let's change our image, what tests should that break❓
75 |
76 | ---
77 |
78 | We're going to update the React image to something better. What tests should break?
79 |
80 | 1. Drag n drop a new image to the `testing-for-charity/my-react-app/src`
81 | 2. In `App.js`, Fix the path of the image to match your new image name `import logo from './mia.jpg';`
82 | 2. Save all files
83 | 3. Stop the React app `ctrl + C` in the server terminal
84 | 4. Restart the app with `npm start`
85 | 5. Rerun the visual tests with `npm run test:visual`
86 | 6. Analyze the results in Screener dashboard
87 |
88 | ## Add a step to CI
89 |
90 | In your `yml` add this code at the end
91 |
92 | ```yml
93 | - name: Run visual tests 👁
94 | run: |
95 | cd testing-for-charity/my-react-app
96 | npm run test:visual
97 | ```
98 |
99 | Push the code to Github
100 |
101 | ## 📝Summary
102 |
103 | ✅Visual e2e testing is a simple and efficient way to ensure visual consistency cross-platform and cross-OS
104 |
105 | ✅We used WebdriverIO + Screener.io to write our visual e2e tests
106 |
107 | ## ⏭️[Let's make our testing more efficient with component tests](./COMPONENT-TESTS.md)
108 |
--------------------------------------------------------------------------------
/testing-for-charity/my-react-app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "my-react-app",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^5.14.1",
7 | "@testing-library/react": "^12.1.0",
8 | "@testing-library/user-event": "^13.2.1",
9 | "@wdio/cli": "^7.13.0",
10 | "react": "^17.0.2",
11 | "react-dom": "^17.0.2",
12 | "react-scripts": "4.0.3",
13 | "web-vitals": "^2.1.0"
14 | },
15 | "scripts": {
16 | "start": "react-scripts start",
17 | "build": "react-scripts build",
18 | "test": "react-scripts test",
19 | "eject": "react-scripts eject",
20 | "cy:open": "cypress open",
21 | "cy:ci": "cypress run",
22 | "test:visual": "wdio run ./wdio.conf.js",
23 | "test:debug": "node --inspect-brk ./node_modules/jest/bin/jest.js --runInBand --watch"
24 | },
25 | "eslintConfig": {
26 | "extends": [
27 | "react-app",
28 | "react-app/jest"
29 | ]
30 | },
31 | "browserslist": {
32 | "production": [
33 | ">0.2%",
34 | "not dead",
35 | "not op_mini all"
36 | ],
37 | "development": [
38 | "last 1 chrome version",
39 | "last 1 firefox version",
40 | "last 1 safari version"
41 | ]
42 | },
43 | "devDependencies": {
44 | "@wdio/local-runner": "^7.13.0",
45 | "@wdio/mocha-framework": "^7.13.0",
46 | "@wdio/sauce-service": "^7.13.0",
47 | "@wdio/spec-reporter": "^7.13.0",
48 | "@wdio/sync": "^7.13.0",
49 | "chromedriver": "^93.0.1",
50 | "cypress": "^8.6.0",
51 | "wdio-chromedriver-service": "^7.2.2"
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/testing-for-charity/my-react-app/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saucelabs-training/automation-best-practices/82c7279cfa1bc88858b0d4be69332d0a29092a9e/testing-for-charity/my-react-app/public/favicon.ico
--------------------------------------------------------------------------------
/testing-for-charity/my-react-app/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 | You need to enable JavaScript to run this app.
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/testing-for-charity/my-react-app/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saucelabs-training/automation-best-practices/82c7279cfa1bc88858b0d4be69332d0a29092a9e/testing-for-charity/my-react-app/public/logo192.png
--------------------------------------------------------------------------------
/testing-for-charity/my-react-app/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saucelabs-training/automation-best-practices/82c7279cfa1bc88858b0d4be69332d0a29092a9e/testing-for-charity/my-react-app/public/logo512.png
--------------------------------------------------------------------------------
/testing-for-charity/my-react-app/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/testing-for-charity/my-react-app/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/testing-for-charity/my-react-app/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | height: 40vmin;
7 | pointer-events: none;
8 | }
9 |
10 | @media (prefers-reduced-motion: no-preference) {
11 | .App-logo {
12 | /*animation: App-logo-spin infinite 20s linear;*/
13 | }
14 | }
15 |
16 | .App-header {
17 | background-color: #282c34;
18 | min-height: 100vh;
19 | display: flex;
20 | flex-direction: column;
21 | align-items: center;
22 | justify-content: center;
23 | font-size: calc(10px + 2vmin);
24 | color: white;
25 | }
26 |
27 | .App-link {
28 | color: #61dafb;
29 | }
30 |
31 | @keyframes App-logo-spin {
32 | from {
33 | transform: rotate(0deg);
34 | }
35 | to {
36 | transform: rotate(360deg);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/testing-for-charity/my-react-app/src/App.js:
--------------------------------------------------------------------------------
1 | import './App.css';
2 | import logo from './mia.jpg';
3 |
4 | function App() {
5 | return (
6 |
23 | );
24 | }
25 |
26 | export default App;
27 |
--------------------------------------------------------------------------------
/testing-for-charity/my-react-app/src/__tests__/App.test.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { render, screen } from '@testing-library/react';
3 | import App from '../App';
4 |
5 | //Using Jest matchers: https://jestjs.io/docs/using-matchers
6 |
7 | test('renders learn react link', () => {
8 | //render our App component in a virtual DOM
9 | render( );
10 | //search for an element by text
11 | const linkElement = screen.getByText('Learn React')
12 | //expect this element to be present in the HTML
13 | expect(linkElement).toBeInTheDocument();
14 | })
--------------------------------------------------------------------------------
/testing-for-charity/my-react-app/src/__tests__/Exercise.test.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { render, screen } from '@testing-library/react';
3 | import App from '../App';
4 |
5 | //Using Jest matchers: https://jestjs.io/docs/using-matchers
6 | test('link has correct url', () => {
7 | /** Your code below */
8 |
9 | //1. render our App component in a virtual DOM
10 | render( );
11 | //2. search for an element by text
12 | const linkElement = screen.getByText('Learn React')
13 | //3. search for href
14 | expect(linkElement.href).toContain('ultimateqa');
15 |
16 | /** Your code above */
17 | })
18 |
19 | test('link opens in new tab', () => {
20 | /** Your code below */
21 |
22 | //1. render our App component in a virtual DOM
23 | //2. get the link element
24 | //3. expect `linkElement.target` toBe('_blank')
25 |
26 | /** Your code above */
27 | })
28 |
29 |
--------------------------------------------------------------------------------
/testing-for-charity/my-react-app/src/__tests__/Solution.test.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { render, screen } from '@testing-library/react';
3 | import App from '../App';
4 |
5 | //Using Jest matchers: https://jestjs.io/docs/using-matchers
6 |
7 | test('renders learn react link', () => {
8 | //render our App component in a virtual DOM
9 | render( );
10 | //search for an element by text
11 | const linkElement = screen.getByText('Learn Testing with Mia')
12 |
13 | //Search for element by test id
14 | //const linkElement = screen.getByTestId('learn-link');
15 |
16 | //Using Jest matchers: https://jestjs.io/docs/using-matchers
17 | //expect this element to be present in the HTML
18 | expect(linkElement).toBeInTheDocument();
19 | })
20 |
21 | test('link has correct url', () => {
22 | //render our App component in a virtual DOM
23 | render( );
24 | const linkElement = screen.getByText('Learn Testing with Mia')
25 | expect(linkElement.href).toContain('ultimateqa');
26 | })
27 |
28 | test('link opens in new tab', () => {
29 | //render our App component in a virtual DOM
30 | render( );
31 | const linkElement = screen.getByText('Learn Testing with Mia')
32 | //Link should open a new tab
33 | expect(linkElement.target).toBe('_blank')
34 | })
35 |
--------------------------------------------------------------------------------
/testing-for-charity/my-react-app/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/testing-for-charity/my-react-app/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import reportWebVitals from './reportWebVitals';
6 |
7 | ReactDOM.render(
8 |
9 |
10 | ,
11 | document.getElementById('root')
12 | );
13 |
14 | // If you want to start measuring performance in your app, pass a function
15 | // to log results (for example: reportWebVitals(console.log))
16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
17 | reportWebVitals();
18 |
--------------------------------------------------------------------------------
/testing-for-charity/my-react-app/src/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/testing-for-charity/my-react-app/src/mia.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saucelabs-training/automation-best-practices/82c7279cfa1bc88858b0d4be69332d0a29092a9e/testing-for-charity/my-react-app/src/mia.jpg
--------------------------------------------------------------------------------
/testing-for-charity/my-react-app/src/mia2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saucelabs-training/automation-best-practices/82c7279cfa1bc88858b0d4be69332d0a29092a9e/testing-for-charity/my-react-app/src/mia2.jpg
--------------------------------------------------------------------------------
/testing-for-charity/my-react-app/src/reportWebVitals.js:
--------------------------------------------------------------------------------
1 | const reportWebVitals = onPerfEntry => {
2 | if (onPerfEntry && onPerfEntry instanceof Function) {
3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4 | getCLS(onPerfEntry);
5 | getFID(onPerfEntry);
6 | getFCP(onPerfEntry);
7 | getLCP(onPerfEntry);
8 | getTTFB(onPerfEntry);
9 | });
10 | }
11 | };
12 |
13 | export default reportWebVitals;
14 |
--------------------------------------------------------------------------------
/testing-for-charity/my-react-app/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/testing-for-charity/my-react-app/test/specs/visual.solution.spec.js:
--------------------------------------------------------------------------------
1 | describe('My app', () => {
2 | it('should look correct', async () => {
3 | await browser.url('');
4 | await browser.execute('/*@visual.init*/', 'My React App');
5 | await browser.execute('/*@visual.snapshot*/', 'Home Page');
6 |
7 | const result = await browser.execute('/*@visual.end*/');
8 | expect(result.message).toBeNull();
9 | });
10 | });
11 |
12 |
--------------------------------------------------------------------------------
/testing-for-charity/my-react-app/wdio.conf.js:
--------------------------------------------------------------------------------
1 | const visualOptions = {
2 | apiKey: process.env.SCREENER_API_KEY,
3 | projectName: 'testing-for-good'
4 | };
5 | const sauceOptions = {
6 | username: process.env.SAUCE_USERNAME,
7 | accesskey: process.env.SAUCE_ACCESS_KEY
8 | };
9 |
10 | exports.config = {
11 | runner: 'local',
12 | user: process.env.SAUCE_USERNAME,
13 | key: process.env.SAUCE_ACCESS_KEY,
14 | region: 'us',
15 | services: [
16 | ['sauce', {
17 | sauceConnect: true
18 | }]
19 | ],
20 | specs: [
21 | './test/specs/**/*.js'
22 | ],
23 | // Patterns to exclude.
24 | exclude: [
25 | // 'path/to/excluded/files'
26 | ],
27 | //
28 | // ============
29 | // Capabilities
30 | // ============
31 | maxInstances: 100,
32 | //Screener config
33 | hostname: 'hub.screener.io',
34 | port: 443,
35 | protocol: 'https',
36 | path: '/wd/hub',
37 | capabilities: [
38 | //Desktop A 28%: https://www.w3schools.com/browsers/browsers_display.asp
39 | {
40 | browserName: 'chrome',
41 | platformName: 'windows 10',
42 | browserVersion: 'latest',
43 | 'sauce:options': {
44 | ...sauceOptions,
45 | },
46 | 'sauce:visual': {
47 | ...visualOptions,
48 | viewportSize: '1366x768'
49 | }
50 | },
51 | {
52 | browserName: 'safari',
53 | platformName: 'macOS 10.15',
54 | browserVersion: 'latest',
55 | 'sauce:options': {
56 | ...sauceOptions,
57 | },
58 | 'sauce:visual': {
59 | ...visualOptions,
60 | viewportSize: '1366x768'
61 | }
62 | },
63 | // // https://yesviz.com/iphones.php
64 | // // iphone12
65 | // {
66 | // browserName: 'safari',
67 | // platformName: 'macOS 10.15',
68 | // browserVersion: 'latest',
69 | // 'sauce:options': {
70 | // ...sauceOptions,
71 | // },
72 | // 'sauce:visual': {
73 | // ...visualOptions,
74 | // viewportSize: '390x844'
75 | // }
76 | // },
77 | // //12 pro max
78 | // {
79 | // browserName: 'safari',
80 | // platformName: 'macOS 10.15',
81 | // browserVersion: 'latest',
82 | // 'sauce:options': {
83 | // ...sauceOptions,
84 | // },
85 | // 'sauce:visual': {
86 | // ...visualOptions,
87 | // viewportSize: '428x926'
88 | // }
89 | // }
90 | ],
91 | // Level of logging verbosity: trace | debug | info | warn | error | silent
92 | logLevel: 'debug',
93 | // bail (default is 0 - don't bail, run all tests).
94 | bail: 0,
95 | baseUrl: 'http://localhost:3000',
96 | //
97 | // Default timeout for all waitFor* commands.
98 | waitforTimeout: 10000,
99 | //
100 | // Default timeout in milliseconds for request
101 | // if browser driver or grid doesn't send response
102 | connectionRetryTimeout: 120000,
103 | //
104 | // Default request retries count
105 | connectionRetryCount: 3,
106 |
107 | framework: 'mocha',
108 | reporters: ['spec'],
109 | //
110 | // Options to be passed to Mocha.
111 | // See the full list at http://mochajs.org/
112 | mochaOpts: {
113 | ui: 'bdd',
114 | timeout: 60000
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/visual-demo/README.md:
--------------------------------------------------------------------------------
1 | # Visual Testing Advanced POC
2 |
3 | To get started
4 |
5 | ```
6 | git clone https://github.com/saucelabs-training/automation-best-practices/
7 | cd visual-demo
8 | npm i
9 | ```
10 |
11 | Run tests on chrome
12 |
13 | ```nodejs
14 | npm run test:visual:chrome
15 | ```
16 |
17 | Run tests on safari
18 |
19 | ```nodejs
20 | npm run test:visual:safari
21 | ```
22 |
--------------------------------------------------------------------------------
/visual-demo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "my-react-app",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@wdio/cli": "^7.13.0"
7 | },
8 | "scripts": {
9 | "test:visual": "wdio run ./wdio.conf.js",
10 | "test:visual:chrome": "wdio run ./wdio.chrome.conf.js",
11 | "test:visual:safari": "wdio run ./wdio.safari.conf.js",
12 | "test:debug": "node --inspect-brk ./node_modules/jest/bin/jest.js --runInBand --watch"
13 | },
14 | "eslintConfig": {
15 | "extends": [
16 | "react-app",
17 | "react-app/jest"
18 | ]
19 | },
20 | "browserslist": {
21 | "production": [
22 | ">0.2%",
23 | "not dead",
24 | "not op_mini all"
25 | ],
26 | "development": [
27 | "last 1 chrome version",
28 | "last 1 firefox version",
29 | "last 1 safari version"
30 | ]
31 | },
32 | "devDependencies": {
33 | "@wdio/local-runner": "^7.13.0",
34 | "@wdio/mocha-framework": "^7.13.0",
35 | "@wdio/sauce-service": "^7.13.0",
36 | "@wdio/spec-reporter": "^7.13.0",
37 | "@wdio/sync": "^7.13.0",
38 | "chromedriver": "^93.0.1",
39 | "wdio-chromedriver-service": "^7.2.2"
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/visual-demo/pageObject/home.page.js:
--------------------------------------------------------------------------------
1 | var Page = require('./page');
2 | var countryModal = require('./modals/choseCountryModal');
3 | var discountModal = require('./modals/discountModal');
4 | var navHeader = require('./components/navHeader');
5 |
6 | class HomePage extends Page {
7 | get countryModal() {
8 | return countryModal;
9 | }
10 |
11 | get discountModal() {
12 | return discountModal;
13 | }
14 |
15 | get navHeader() {
16 | return navHeader;
17 | }
18 |
19 | async closeCountryModal() {
20 | browser.pause(2000);
21 | if(await this.countryModal.isDisplayed())
22 | await this.countryModal.closeModal();
23 |
24 | return this;
25 | }
26 |
27 | async closeDiscountModal() {
28 | await browser.waitUntil(
29 | async () => await this.discountModal.isDisplayed(),
30 | {
31 | timeout: 30000
32 | }
33 | );
34 | await this.discountModal.closeModal();
35 |
36 | return this;
37 | }
38 |
39 | async navigationHeader() {
40 | return await this.navHeader;
41 | }
42 |
43 | async acceptCookies() {
44 | browser.pause(2000);
45 |
46 | if(await $('//button[@id="onetrust-accept-btn-handler"]').isDisplayed())
47 | await $('//button[@id="onetrust-accept-btn-handler"]').click();
48 |
49 | return this;
50 | }
51 | }
52 |
53 | module.exports = new HomePage();
54 |
--------------------------------------------------------------------------------
/visual-demo/pageObject/page.js:
--------------------------------------------------------------------------------
1 | var locales = require('../options/locales.json');
2 |
3 | module.exports = class Page {
4 | open(locale) {
5 | return browser.url(`https://www.lululemon.${locales[locale]}`);
6 | }
7 |
8 | async initialize(testName) {
9 | await browser.execute('/*@visual.init*/', testName);
10 | }
11 |
12 | resolve() {
13 | return browser.execute('/*@visual.end*/');
14 | }
15 |
16 | async makeScreenShot(screenShotName) {
17 | await $('//a[text()="Terms of Use"]').scrollIntoView();
18 | await browser.pause(5000);
19 | await $('//a[@class="minicart-link"]').scrollIntoView();
20 | await browser.execute('/*@visual.snapshot*/', screenShotName);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/visual-demo/test/specs/visualChecks/uk.chrome.spec.js:
--------------------------------------------------------------------------------
1 | describe('United Kingdom region', async () => {
2 | it('HomePage check', async () => {
3 | await browser.url(
4 | 'https://www.lululemon.co.uk/en-gb/p/wunder-puff-crop-vest/prod9960896.html?dwvar_prod9960896_color=0001'
5 | );
6 |
7 | //accept cookies
8 | browser.pause(2000);
9 | if (await $('//button[@id="onetrust-accept-btn-handler"]').isDisplayed()) {
10 | await $('//button[@id="onetrust-accept-btn-handler"]').click();
11 | }
12 |
13 | await $('//a[@data-dismiss="modal"]').click();
14 | await browser.execute('/*@visual.init*/', 'uk', {
15 | ignore: '#nebula_div_btn, .bv-content-list-container',
16 | });
17 |
18 | await browser.execute('/*@visual.snapshot*/', 'Womens jacket');
19 |
20 | await $('//label[@for="12"]').click();
21 |
22 | await $('//*[contains(@class,"add-to-cart-section")]').click();
23 |
24 | await browser.execute('/*@visual.snapshot*/', 'Checkout popup');
25 |
26 | await browser.url('https://www.lululemon.co.uk/en-gb/my-bag');
27 |
28 | await browser.execute('/*@visual.snapshot*/', 'My Bag');
29 |
30 | const result = await browser.execute('/*@visual.end*/');
31 | expect(result.message).toBeNull();
32 | });
33 | });
34 |
--------------------------------------------------------------------------------
/visual-demo/test/specs/visualChecks/uk.safari.spec.js:
--------------------------------------------------------------------------------
1 | var homePage = require('../../../pageObject/home.page');
2 |
3 | describe('United Kingdom region', async () => {
4 | it('HomePage check', async () => {
5 | await browser.url('https://www.lululemon.co.uk/en-gb/p/wunder-puff-crop-vest/prod9960896.html?dwvar_prod9960896_color=0001');
6 |
7 | await homePage.acceptCookies();
8 | await $('//a[@data-dismiss="modal"]').click();
9 |
10 | await browser.execute('/*@visual.init*/', 'uk', {ignore: '#nebula_div_btn, .bv-content-list-container'});
11 |
12 | await browser.execute('/*@visual.snapshot*/', 'Womens jacket');
13 |
14 | await $('//label[@for="12"]').click();
15 |
16 | await $('//*[contains(@class,"add-to-cart-section")]').click();
17 |
18 | //await browser.execute('/*@visual.snapshot*/', 'Checkout popup'); Issue with elements order
19 |
20 | await browser.url('https://www.lululemon.co.uk/en-gb/my-bag');
21 |
22 | await browser.execute('/*@visual.snapshot*/', 'My Bag');
23 |
24 | const result = await browser.execute('/*@visual.end*/');
25 | expect(result.message).toBeNull();
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/visual-demo/test/specs/visualChecks/us.chrome.spec.js:
--------------------------------------------------------------------------------
1 | describe('US region', async () => {
2 | it('HomePage check', async () => {
3 | await browser.url(
4 | 'https://shop.lululemon.com/p/jackets-and-hoodies-jackets/Down-For-It-All-Jacket/_/prod9201505?color=0001&sz=0'
5 | );
6 |
7 | await browser.execute('/*@visual.init*/', 'us', {
8 | ignore: '#nebula_div_btn',
9 | });
10 |
11 | await browser.execute('/*@visual.snapshot*/', 'Womens jacket');
12 |
13 | await $('[data-testid="waypoint-wrapper"]').click();
14 |
15 | await browser.execute('/*@visual.snapshot*/', 'Checkout popup');
16 |
17 | await browser.url('https://shop.lululemon.com/shop/mybag');
18 |
19 | await browser.execute('/*@visual.snapshot*/', 'My Bag');
20 |
21 | const result = await browser.execute('/*@visual.end*/');
22 | expect(result.message).toBeNull();
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/visual-demo/test/specs/visualChecks/us.v2.safariBroken.spec.js:
--------------------------------------------------------------------------------
1 | var homePage = require('../../../pageObject/home.page');
2 |
3 | describe('US region', async () => {
4 | // before(async () => {
5 | // await homePage.open('newZeland');
6 | // });
7 |
8 | it('HomePage check', async () => {
9 | await browser.url('https://shop.lululemon.com/p/jackets-and-hoodies-jackets/Down-For-It-All-Jacket/_/prod9201505?color=0001&sz=0');
10 | //init only with the country name
11 | //await homePage.initialize('newZeland');
12 | //the hardest part is ensuring that the page is in the correct state before a snapshot
13 | //await homePage.closeCountryModal();
14 | await browser.execute('/*@visual.init*/', 'us', {ignore: '#nebula_div_btn'});
15 | //snapshot should contain only the page/component name
16 | await browser.execute('/*@visual.snapshot*/', 'Womens jacket');
17 |
18 | await $('[data-testid="waypoint-wrapper"]').click();
19 |
20 | await browser.url('https://shop.lululemon.com/shop/mybag');
21 |
22 | await $('[data-funding-source="paypal"]').waitForDisplayed();
23 |
24 | await browser.execute('/*@visual.snapshot*/', 'My Bag');
25 |
26 | // await browser.refresh();
27 |
28 | // await $('#accessories-accordion').moveTo();
29 | // await $('#accessories-accordion').$('//a[@aria-label="Bags"]').click();
30 |
31 | // await browser.execute('/*@visual.snapshot*/', 'Bags');
32 |
33 | // await browser.refresh();
34 |
35 | // await $('#accessories-accordion').moveTo();
36 | // await $('#accessories-accordion').$('//a[@aria-label="Equipment"]').click();
37 |
38 | // await browser.execute('/*@visual.snapshot*/', 'Equipment');
39 |
40 | const result = await browser.execute('/*@visual.end*/');
41 | //don't change this assertion
42 | expect(result.message).toBeNull();
43 | });
44 | });
45 |
--------------------------------------------------------------------------------
/visual-demo/test/specs/visualChecks/weirdBehavior.spec.js:
--------------------------------------------------------------------------------
1 | var homePage = require('../../../pageObject/home.page');
2 |
3 | describe('Weird behaviors', async () => {
4 | it('HomePage check', async () => {
5 | await browser.url(
6 | 'https://www.lululemon.co.uk/en-gb/p/wunder-puff-jacket/LW4BTJS.html'
7 | );
8 | await browser.execute('/*@visual.init*/', 'uk', {
9 | ignore: '#nebula_div_btn',
10 | });
11 | await homePage.closeCountryModal();
12 | browser.execute('/*@visual.snapshot*/', 'Wunder Puff Jacket');
13 | const result = await browser.execute('/*@visual.end*/');
14 | expect(result.message).toBeNull();
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/visual-demo/wdio.chrome.conf.js:
--------------------------------------------------------------------------------
1 | const visualOptions = {
2 | apiKey: process.env.SCREENER_API_KEY,
3 | projectName: 'lululemon Chrome',
4 | };
5 | const sauceOptions = {
6 | username: process.env.SAUCE_USERNAME,
7 | accesskey: process.env.SAUCE_ACCESS_KEY,
8 | };
9 | exports.config = {
10 | region: process.env.REGION || 'us',
11 | services: [['sauce']],
12 | specs: ['./test/specs/**/**.chrome.spec.js'],
13 | // Patterns to exclude.
14 | exclude: [
15 | // 'path/to/excluded/files'
16 | ],
17 | maxInstances: 10,
18 | hostname: 'hub.screener.io',
19 | port: 443,
20 | protocol: 'https',
21 | path: '/wd/hub',
22 | capabilities: [
23 | //Desktop A 28%: https://www.w3schools.com/browsers/browsers_display.asp
24 | {
25 | browserName: 'chrome',
26 | platformName: 'windows 10',
27 | browserVersion: 'latest',
28 | 'sauce:options': {
29 | ...sauceOptions,
30 | },
31 | 'sauce:visual': {
32 | ...visualOptions,
33 | viewportSize: '1366x768',
34 | },
35 | },
36 | ],
37 | //
38 | // ===================
39 | // Test Configurations
40 | // ===================
41 | // Level of logging verbosity: trace | debug | info | warn | error | silent
42 | logLevel: 'error',
43 | bail: 0,
44 | baseUrl: '',
45 | waitforTimeout: 10000,
46 | connectionRetryTimeout: 120000,
47 | connectionRetryCount: 3,
48 | framework: 'mocha',
49 | reporters: ['spec'],
50 | mochaOpts: {
51 | ui: 'bdd',
52 | timeout: 200000,
53 | },
54 | before: function (browser) {
55 | browser.overwriteCommand(
56 | 'click',
57 | async function (origClickFunction, { force = false } = {}) {
58 | if (!force) {
59 | try {
60 | return origClickFunction();
61 | } catch (err) {
62 | if (err.message.includes('not clickable at point')) {
63 | await this.scrollIntoView();
64 | return origClickFunction();
65 | }
66 | throw err;
67 | }
68 | }
69 | await browser.execute((el) => {
70 | el.click();
71 | }, this);
72 | },
73 | true
74 | );
75 | },
76 | };
77 |
--------------------------------------------------------------------------------
/visual-demo/wdio.safari.conf.js:
--------------------------------------------------------------------------------
1 | const visualOptions = {
2 | apiKey: process.env.SCREENER_API_KEY,
3 | projectName: 'lululemon Safari',
4 | };
5 | const sauceOptions = {
6 | username: process.env.SAUCE_USERNAME,
7 | accesskey: process.env.SAUCE_ACCESS_KEY,
8 | };
9 | exports.config = {
10 | region: process.env.REGION || 'us',
11 | services: [['sauce']],
12 | specs: ['./test/specs/**/**safari.spec.js'],
13 | // Patterns to exclude.
14 | exclude: [
15 | // 'path/to/excluded/files'
16 | ],
17 | maxInstances: 10,
18 | hostname: 'hub.screener.io',
19 | port: 443,
20 | protocol: 'https',
21 | path: '/wd/hub',
22 | capabilities: [
23 | //Desktop A 28%: https://www.w3schools.com/browsers/browsers_display.asp
24 | {
25 | browserName: 'safari',
26 | platformName: 'macOS 11.00',
27 | browserVersion: 'latest',
28 | 'sauce:options': {
29 | ...sauceOptions,
30 | },
31 | 'sauce:visual': {
32 | ...visualOptions,
33 | viewportSize: '1366x768',
34 | },
35 | },
36 | ],
37 | //
38 | // ===================
39 | // Test Configurations
40 | // ===================
41 | // Level of logging verbosity: trace | debug | info | warn | error | silent
42 | logLevel: 'error',
43 | bail: 0,
44 | baseUrl: '',
45 | waitforTimeout: 10000,
46 | connectionRetryTimeout: 120000,
47 | connectionRetryCount: 3,
48 | framework: 'mocha',
49 | reporters: ['spec'],
50 | mochaOpts: {
51 | ui: 'bdd',
52 | timeout: 200000,
53 | },
54 | before: function (browser) {
55 | browser.overwriteCommand(
56 | 'click',
57 | async function (origClickFunction, { force = false } = {}) {
58 | if (!force) {
59 | try {
60 | return origClickFunction();
61 | } catch (err) {
62 | if (err.message.includes('not clickable at point')) {
63 | await this.scrollIntoView();
64 | return origClickFunction();
65 | }
66 | throw err;
67 | }
68 | }
69 | await browser.execute((el) => {
70 | el.click();
71 | }, this);
72 | },
73 | true
74 | );
75 | },
76 | };
77 |
--------------------------------------------------------------------------------