Skip to content

Commit 53a7094

Browse files
committed
Complete SauceDemo automation project with Cypress
1 parent 4e63a3c commit 53a7094

File tree

18 files changed

+3941
-27
lines changed

18 files changed

+3941
-27
lines changed
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
name: Cypress Tests
2+
3+
on:
4+
push:
5+
branches: [master]
6+
pull_request:
7+
branches: [master]
8+
9+
jobs:
10+
cypress-run:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- name: Checkout
14+
uses: actions/checkout@v3
15+
16+
- name: Setup Node.js
17+
uses: actions/setup-node@v3
18+
with:
19+
node-version: 18
20+
21+
- name: Install dependencies
22+
working-directory: cypress-project
23+
run: npm install
24+
25+
- name: Create env file
26+
working-directory: cypress-project
27+
run: echo '{"password":"secret_sauce"}' > cypress.env.json
28+
29+
- name: Run Cypress tests
30+
working-directory: cypress-project
31+
run: npm run test
32+
33+
- name: Merge Mochawesome reports
34+
if: always()
35+
working-directory: cypress-project
36+
run: |
37+
npx mochawesome-merge cypress/reports/*.json > cypress/reports/mochawesome.json
38+
npx mochawesome-report-generator cypress/reports/mochawesome.json -o cypress/reports/html
39+
40+
- name: Deploy to GitHub Pages
41+
if: success() || failure()
42+
uses: JamesIves/github-pages-deploy-action@v4
43+
with:
44+
folder: cypress-project/cypress/reports/html
45+
branch: gh-pages

cypress-project/.gitignore

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Dependencies
2+
node_modules/
3+
4+
# Cypress generated files
5+
cypress/screenshots/
6+
cypress/videos/
7+
cypress/downloads/
8+
cypress/reports/
9+
10+
# Environment files with sensitive data
11+
cypress.env.json
12+
13+
# IDE files
14+
.idea/
15+
.vscode/
16+
17+
# OS generated files
18+
.DS_Store

cypress-project/README.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# SauceDemo Cypress Tests
2+
3+
This project contains automated tests for the SauceDemo website using Cypress with TypeScript and following the Page Object Model pattern.
4+
5+
## Project Structure
6+
7+
```
8+
cypress-project/
9+
├── cypress/
10+
│ ├── e2e/ # Test files
11+
│ │ ├── valid-login.cy.ts
12+
│ │ └── locked-out-user.cy.ts
13+
│ ├── fixtures/ # Test data
14+
│ │ └── users.json
15+
│ ├── pages/ # Page Object Models
16+
│ │ ├── LoginPage.ts
17+
│ │ └── ProductsPage.ts
18+
│ └── support/ # Support files
19+
│ ├── commands.ts # Custom commands
20+
│ └── e2e.ts # Configuration
21+
├── cypress.config.ts # Cypress configuration
22+
├── cypress.env.json # Environment variables (not committed to Git)
23+
├── package.json # Project dependencies
24+
├── tsconfig.json # TypeScript configuration
25+
└── README.md # Project documentation
26+
```
27+
28+
## Features
29+
30+
- Two test scenarios: Valid Login and Locked Out User
31+
- Page Object Model for better maintainability
32+
- TypeScript for type safety
33+
- Mochawesome reporter for HTML test reports
34+
- Custom task for detailed logging
35+
- GitHub Actions workflow for CI
36+
37+
## Installation
38+
39+
1. Clone the repository
40+
2. Install dependencies:
41+
42+
```bash
43+
cd cypress-project
44+
npm install
45+
```
46+
47+
## Running Tests
48+
49+
### Headless Mode
50+
51+
To run all tests in headless mode:
52+
53+
```bash
54+
npm run test
55+
```
56+
57+
### UI Mode
58+
59+
To open Cypress in UI mode:
60+
61+
```bash
62+
npm run cypress:open
63+
```
64+
65+
## Reports
66+
67+
After running tests, HTML reports will be generated in the `cypress/reports` directory.

cypress-project/cypress.config.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { defineConfig } from 'cypress';
2+
3+
export default defineConfig({
4+
e2e: {
5+
setupNodeEvents(on, config) {
6+
// Register tasks
7+
on('task', {
8+
log(message: string) {
9+
console.log(`\n--- TEST LOG: ${message}\n`);
10+
return null;
11+
}
12+
});
13+
},
14+
baseUrl: 'https://www.saucedemo.com',
15+
specPattern: 'cypress/e2e/**/*.cy.{js,jsx,ts,tsx}',
16+
},
17+
reporter: 'cypress-multi-reporters',
18+
reporterOptions: {
19+
reporterEnabled: 'mochawesome',
20+
mochawesomeReporterOptions: {
21+
reportDir: 'cypress/reports',
22+
quite: true,
23+
overwrite: false,
24+
html: true,
25+
json: true
26+
}
27+
},
28+
video: false,
29+
viewportWidth: 1280,
30+
viewportHeight: 720,
31+
defaultCommandTimeout: 10000
32+
});
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import LoginPage from '../pages/LoginPage';
2+
3+
describe('Locked Out User Test', () => {
4+
const loginPage = new LoginPage();
5+
6+
beforeEach(() => {
7+
cy.fixture('users').then(function (users) {
8+
this.users = users;
9+
});
10+
});
11+
12+
it('should show error message for locked out user', function () {
13+
// Visit the login page
14+
loginPage.visit();
15+
16+
// Login with locked out user
17+
loginPage.login(this.users.lockedOutUser.username, Cypress.env('password'));
18+
19+
// Verify error message
20+
loginPage.getErrorMessage()
21+
.should('be.visible')
22+
.and('contain.text', 'Sorry, this user has been locked out');
23+
24+
cy.log('Verified locked out user error message');
25+
});
26+
});
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import LoginPage from '../pages/LoginPage';
2+
import ProductsPage from '../pages/ProductsPage';
3+
4+
describe('Valid Login Test', () => {
5+
const loginPage = new LoginPage();
6+
const productsPage = new ProductsPage();
7+
8+
beforeEach(() => {
9+
cy.fixture('users').then(function (users) {
10+
this.users = users;
11+
});
12+
});
13+
14+
it('should successfully login with valid user', function () {
15+
// Visit the login page
16+
loginPage.visit();
17+
18+
// Login with standard user
19+
loginPage.login(this.users.standardUser.username, Cypress.env('password'));
20+
21+
// Verify successful login
22+
productsPage.isVisible();
23+
productsPage.getInventoryItems().should('have.length.at.least', 1);
24+
25+
// Logout
26+
loginPage.logout();
27+
});
28+
});
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"standardUser": {
3+
"username": "standard_user"
4+
},
5+
"lockedOutUser": {
6+
"username": "locked_out_user"
7+
}
8+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
class LoginPage {
2+
private usernameInput = '#user-name';
3+
private passwordInput = '#password';
4+
private loginButton = '#login-button';
5+
private errorMessage = '[data-test="error"]';
6+
private menuButton = '#react-burger-menu-btn';
7+
private logoutLink = '#logout_sidebar_link';
8+
9+
visit() {
10+
cy.visit('https://www.saucedemo.com');
11+
return this;
12+
}
13+
14+
login(username: string, password: string) {
15+
cy.get(this.usernameInput).type(username);
16+
cy.get(this.passwordInput).type(password);
17+
cy.get(this.loginButton).click();
18+
19+
cy.log(`Logging in with username: ${username}`);
20+
return this;
21+
}
22+
23+
getErrorMessage() {
24+
return cy.get(this.errorMessage);
25+
}
26+
27+
logout() {
28+
cy.get(this.menuButton).click();
29+
cy.get(this.logoutLink).click();
30+
31+
cy.log('Logging out');
32+
return this;
33+
}
34+
}
35+
36+
export default LoginPage;
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
class ProductsPage {
2+
private productsTitle = '.title';
3+
private inventoryItems = '.inventory_item';
4+
5+
isVisible() {
6+
cy.get(this.productsTitle).should('have.text', 'Products');
7+
cy.task('log', 'Verifying Products page is visible');
8+
return this;
9+
}
10+
11+
getInventoryItems() {
12+
return cy.get(this.inventoryItems);
13+
}
14+
}
15+
16+
export default ProductsPage;
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// ***********************************************
2+
// This example commands.ts 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+
// -- This is a parent command --
12+
// Cypress.Commands.add('login', (email, password) => { ... })
13+
14+
// -- This is a child command --
15+
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
16+
17+
// -- This is a dual command --
18+
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
19+
20+
// -- This will overwrite an existing command --
21+
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
22+
23+
export {};

0 commit comments

Comments
 (0)