diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 00000000..b58b603f --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,5 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/Playwright-Cucumber-Exercise.iml b/.idea/Playwright-Cucumber-Exercise.iml new file mode 100644 index 00000000..d0876a78 --- /dev/null +++ b/.idea/Playwright-Cucumber-Exercise.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 00000000..105ce2da --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 00000000..91d4f8f5 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..35eb1ddf --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/features/login.feature b/features/login.feature index fb9f1fa5..66925fdb 100644 --- a/features/login.feature +++ b/features/login.feature @@ -5,8 +5,10 @@ Feature: Login Feature Scenario: Validate the login page title # TODO: Fix this failing scenario - Then I should see the title "Labs Swag" + Then I should see the title "Swag Labs" Scenario: Validate login error message Then I will login as 'locked_out_user' - # TODO: Add a step to validate the error message received \ No newline at end of file + # TODO: Add a step to validate the error message received + Then the url should be "https://www.saucedemo.com/" + Then I should see the error "Epic sadface: Sorry, this user has been locked out." \ No newline at end of file diff --git a/features/product.feature b/features/product.feature index 8a7ceab9..afb25037 100644 --- a/features/product.feature +++ b/features/product.feature @@ -7,7 +7,11 @@ Feature: Product Feature Scenario Outline: Validate product sort by price Then I will login as 'standard_user' # TODO: Sort the items by + Then I will sort the items from "" # TODO: Validate all 6 items are sorted correctly by price + Then I should see the products listed by price from "" Examples: - # TODO: extend the datatable to paramterize this test - | sort | \ No newline at end of file + # TODO: extend the datatable to parameterize this test + | sort | + | low to high | + | high to low | \ No newline at end of file diff --git a/features/purchase.feature b/features/purchase.feature index 28634789..f1f3ae28 100644 --- a/features/purchase.feature +++ b/features/purchase.feature @@ -4,11 +4,17 @@ Feature: Purchase Feature Given I open the "https://www.saucedemo.com/" page Scenario: Validate successful purchase text - Then I will login as 'standard_user' + Then I will login with 'standard_user' as the user Then I will add the backpack to the cart # TODO: Select the cart (top-right) + Then I will go to the cart # TODO: Select Checkout + Then I will click 'checkout' # TODO: Fill in the First Name, Last Name, and Zip/Postal Code + Then I will fill in my First Name, Last Name, and Postal Code # TODO: Select Continue + Then I will select continue # TODO: Select Finish - # TODO: Validate the text 'Thank you for your order!' \ No newline at end of file + Then I will click 'finish' + # TODO: Validate the text 'Thank you for your order!' + Then I should see the header 'Thank you for your order!' \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index b90d7c6c..b26590ba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "Playwright-Project", + "name": "Playwright-Cucumber-Exercise", "lockfileVersion": 3, "requires": true, "packages": { @@ -189,6 +189,7 @@ "resolved": "https://registry.npmjs.org/@cucumber/cucumber/-/cucumber-10.0.1.tgz", "integrity": "sha512-g7W7SQnNMSNnMRQVGubjefCxdgNFyq4P3qxT2Ve7Xhh8ZLoNkoRDcWsyfKQVWnxNfgW3aGJmxbucWRoTi+ZUqg==", "dev": true, + "peer": true, "dependencies": { "@cucumber/ci-environment": "9.2.0", "@cucumber/cucumber-expressions": "16.1.2", @@ -253,6 +254,7 @@ "resolved": "https://registry.npmjs.org/@cucumber/gherkin/-/gherkin-26.2.0.tgz", "integrity": "sha512-iRSiK8YAIHAmLrn/mUfpAx7OXZ7LyNlh1zT89RoziSVCbqSVDxJS6ckEzW8loxs+EEXl0dKPQOXiDmbHV+C/fA==", "dev": true, + "peer": true, "dependencies": { "@cucumber/messages": ">=19.1.4 <=22" } @@ -350,6 +352,7 @@ "resolved": "https://registry.npmjs.org/@cucumber/message-streams/-/message-streams-4.0.1.tgz", "integrity": "sha512-Kxap9uP5jD8tHUZVjTWgzxemi/0uOsbGjd4LBOSxcJoOCRbESFwemUzilJuzNTB8pcTQUh8D5oudUyxfkJOKmA==", "dev": true, + "peer": true, "peerDependencies": { "@cucumber/messages": ">=17.1.1" } @@ -359,6 +362,7 @@ "resolved": "https://registry.npmjs.org/@cucumber/messages/-/messages-22.0.0.tgz", "integrity": "sha512-EuaUtYte9ilkxcKmfqGF9pJsHRUU0jwie5ukuZ/1NPTuHS1LxHPsGEODK17RPRbZHOFhqybNzG2rHAwThxEymg==", "dev": true, + "peer": true, "dependencies": { "@types/uuid": "9.0.1", "class-transformer": "0.5.1", @@ -549,6 +553,7 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.3.tgz", "integrity": "sha512-XJavIpZqiXID5Yxnxv3RUDKTN5b81ddNC3ecsA0SoFXz/QU8OGBwZGMomiq0zw+uuqbL/krztv/DINAQ/EV4gg==", "dev": true, + "peer": true, "dependencies": { "undici-types": "~5.26.4" } @@ -974,6 +979,7 @@ "resolved": "https://registry.npmjs.org/@cucumber/messages/-/messages-21.0.1.tgz", "integrity": "sha512-pGR7iURM4SF9Qp1IIpNiVQ77J9kfxMkPOEbyy+zRmGABnWWCsqMpJdfHeh9Mb3VskemVw85++e15JT0PYdcR3g==", "dev": true, + "peer": true, "dependencies": { "@types/uuid": "8.3.4", "class-transformer": "0.5.1", @@ -2473,6 +2479,7 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz", "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==", "dev": true, + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/pages/login.page.ts b/pages/login.page.ts index 5a01614b..90d1daa1 100644 --- a/pages/login.page.ts +++ b/pages/login.page.ts @@ -1,4 +1,5 @@ import { Page } from "@playwright/test" +import exp from "node:constants"; export class Login { private readonly page: Page @@ -6,6 +7,7 @@ export class Login { private readonly passwordField: string = 'input[id="password"]' private readonly userNameField: string = 'input[id="user-name"]' private readonly loginButton: string = 'input[id="login-button"]' + private readonly errorBox:string = 'h3[data-test="error"]' constructor(page: Page) { this.page = page; @@ -23,4 +25,27 @@ export class Login { await this.page.locator(this.passwordField).fill(this.password) await this.page.locator(this.loginButton).click() } -} \ No newline at end of file + + public async validateUrl(expectedUrl:string) + { + const pageUrl = this.page.url(); + if (pageUrl !== expectedUrl) + { + throw new Error(`Expected url to be ${expectedUrl} but found ${pageUrl}`) + } + } + + public async validateError(expectedError: string) + { + const errorText:string|null = await this.page.locator(this.errorBox).textContent() + + // const errorText:string|null = await this.page.locator("").getAttribute("dir") + + if (errorText !== expectedError) + { + throw new Error(`Expected error to be "${expectedError}" but found "${errorText}"`) + } + } +} + + diff --git a/pages/product.page.ts b/pages/product.page.ts index 14bedb1b..89905e6b 100644 --- a/pages/product.page.ts +++ b/pages/product.page.ts @@ -3,6 +3,11 @@ import { Page } from "@playwright/test" export class Product { private readonly page: Page private readonly addToCart: string = 'button[id="add-to-cart-sauce-labs-backpack"]' + private readonly sortDropDown: string = 'select [class="product_sort_container"]' + private readonly hilo: string = 'option[value="hilo"]' + private readonly lohi: string = 'option[value="lohi"]' + private readonly inventoryList: string = 'div[class="inventory_list"]' + constructor(page: Page) { this.page = page; @@ -11,4 +16,31 @@ export class Product { public async addBackPackToCart() { await this.page.locator(this.addToCart).click() } + + public async sortHighToLow(){ + await this.page.locator(this.sortDropDown).click() + await this.page.locator(this.hilo).click() + } + + public async sortLowToHigh(){ + await this.page.locator(this.sortDropDown).click() + await this.page.locator(this.lohi).click() + } + + public async getListedPrices():Promise + { + let itemList = await this.page.locator(this.inventoryList).allTextContents() + + throw new Error(`recieved ${itemList}`) + + + + } + + + + + + + } \ No newline at end of file diff --git a/pages/purchase.page.ts b/pages/purchase.page.ts new file mode 100644 index 00000000..4aeb2871 --- /dev/null +++ b/pages/purchase.page.ts @@ -0,0 +1,67 @@ +import { Page } from "@playwright/test" +import exp from "node:constants"; + +export class Purchase { + // constants + private readonly page: Page + private readonly password: string = 'secret_sauce' + private readonly firstName: string = 'standard' + private readonly lastName: string = 'user' + private readonly postalCode: string = '12345' + private readonly passwordField: string = 'input[id="password"]' + private readonly userNameField: string = 'input[id="user-name"]' + private readonly loginButton: string = 'input[id="login-button"]' + private readonly firstNameField: string = 'input[id="first-name"]' + private readonly lastNameField: string = 'input[id="last-name"]' + private readonly postalCodeField: string = 'input[id="postal-code"]' + private readonly completeHeader: string = 'h2[class="complete-header"]' + private readonly continueButton: string = 'input[id="continue"]' + + + constructor(page: Page) { + this.page = page; + } + + public async loginAsUser(userName: string) { + await this.page.locator(this.userNameField).fill(userName) + await this.page.locator(this.passwordField).fill(this.password) + await this.page.locator(this.loginButton).click() + } + + public async goToCart(){ + await this.page.locator('a[class="shopping_cart_link"]').click() + } + + public async click(clickId: string){ + await this.page.locator(`button[id="${clickId}"]`).click() + } + + public async clickContinue(){ + await this.page.locator(this.continueButton).click() + } + + public async fillInInfo(){ + await this.page.locator(this.firstNameField).fill(this.firstName) + await this.page.locator(this.lastNameField).fill(this.lastName) + await this.page.locator(this.postalCodeField).fill(this.postalCode) + } + + public async validateComplete(expectedHeader: string){ + const headerText:string|null = await this.page.locator(this.completeHeader).textContent() + + if (headerText !== expectedHeader) + { + throw new Error(`Expected complete header to be "${expectedHeader}" but found "${headerText}"`) + } + } +} + + + + + + + + + + diff --git a/steps/login.steps.ts b/steps/login.steps.ts index c2aa0d80..afbf1673 100644 --- a/steps/login.steps.ts +++ b/steps/login.steps.ts @@ -8,4 +8,19 @@ Then('I should see the title {string}', async (expectedTitle) => { Then('I will login as {string}', async (userName) => { await new Login(getPage()).loginAsUser(userName); -}); \ No newline at end of file +}); + +Then('the url should be {string}', async (url) => +{ + await new Login(getPage()).validateUrl(url); +}); + +Then('I should see the error {string}', async (errorMessage) => +{ + // const login = new Login(getPage()) + // await login.loginAsUser("locked_out_user"); + // await login.validateError(errorMessage); + + await new Login(getPage()).validateError(errorMessage) +}); + diff --git a/steps/product.steps.ts b/steps/product.steps.ts index bb52fb98..143815d3 100644 --- a/steps/product.steps.ts +++ b/steps/product.steps.ts @@ -4,4 +4,23 @@ import { Product } from '../pages/product.page'; Then('I will add the backpack to the cart', async () => { await new Product(getPage()).addBackPackToCart(); -}); \ No newline at end of file +}); + +Then('Then I will sort the items from high to low', async () => { + await new Product(getPage()).sortHighToLow(); +}) + +Then('Then I will sort the items from low to high', async () => { + await new Product(getPage()).sortLowToHigh(); +}) + +Then('I should see the products listed by price from {string}', async (sort: string) => +{ + await new Product(getPage()).getListedPrices() +}) + +Then('Then I will sort the items from{string}', async (sort:string) => + { + await new Product(getPage()).sortHighToLow(); + } +) \ No newline at end of file diff --git a/steps/purchase.steps.ts b/steps/purchase.steps.ts new file mode 100644 index 00000000..1b8d92e1 --- /dev/null +++ b/steps/purchase.steps.ts @@ -0,0 +1,42 @@ +import { Then } from '@cucumber/cucumber'; +import { getPage } from '../playwrightUtilities'; +import { Purchase } from '../pages/purchase.page'; +import { Login } from '../pages/login.page'; +import * as punycode from "node:punycode"; + + +Then('I will login with {string} as the user', async (userName) => { + await new Login(getPage()).loginAsUser(userName); +}); + +// Then('I will add the backpack to the cart', async () => +// { +// await new Purchase(getPage()).click('add-to-cart-sauce-labs-backpack'); +// }); + +Then('I will go to the cart', async () => +{ + await new Purchase(getPage()).goToCart(); +}); + +Then('I will click {string}', async (clickId) => +{ + await new Purchase(getPage()).click(clickId); +}); + +Then('I will fill in my First Name, Last Name, and Postal Code', async () => +{ + await new Purchase(getPage()).fillInInfo(); +}); + +Then('I will select continue', async () => +{ + await new Purchase(getPage()).clickContinue() +}); + + + +Then('I should see the header {string}', async (expectedHeader) => +{ + await new Purchase(getPage()).validateComplete(expectedHeader) +}); \ No newline at end of file