Created
September 6, 2025 12:14
-
-
Save minhphong306/0906109bd7ea8a7b072173c5136dc761 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { test, expect, Page } from '@playwright/test'; | |
| import { faker } from '@faker-js/faker'; | |
| import path from 'path'; | |
| test.describe('Lesson 02 - Ex 01', async () => { | |
| const baseUrl = 'https://material.playwrightvn.com/'; | |
| const registerData = { | |
| username: faker.person.fullName(), | |
| email: faker.internet.email(), | |
| gender: faker.helpers.arrayElement(['Male', 'Female']), | |
| hobbies: ['traveling'], | |
| interests: [ | |
| { value: 'technology' }, | |
| { value: 'science' }, | |
| { value: 'music' }, | |
| ], | |
| country: 'usa', | |
| dateOfBirth: faker.date.birthdate().toISOString().split('T')[0], | |
| biography: faker.lorem.sentence(), | |
| ratingUsOutOfTen: faker.number.int({ min: 1, max: 10 }).toString(), | |
| favoriteColor: faker.color.rgb(), | |
| newsletter: faker.datatype.boolean(0.75), | |
| enableFeature: faker.datatype.boolean(0.75), | |
| ratingStarOutOfFive: faker.number.float({ min: 0, max: 4.9, fractionDigits: 1 }), | |
| customDate: faker.date.birthdate().toISOString().split('T')[0] | |
| }; | |
| const shoppingCartData = { | |
| product1: { | |
| id: 1, | |
| name: 'Product 1', | |
| price: 10.00, | |
| quantity: 2, | |
| }, | |
| product2: { | |
| id: 2, | |
| name: 'Product 2', | |
| price: 20.00, | |
| quantity: 3, | |
| }, | |
| product3: { | |
| id: 3, | |
| name: 'Product 3', | |
| price: 30.00, | |
| quantity: 1, | |
| } | |
| } | |
| test.beforeEach(async ({ page }) => { | |
| await test.step(`Step 01: Navigate to the page: ${baseUrl}`, async () => { | |
| await page.goto(baseUrl); | |
| }); | |
| }); | |
| test('Test 01 - Register Page', async ({ page }) => { | |
| await test.step('Step 02: Click to "Bài học 1: Register Page" then verify "User Registration" title display', async () => { | |
| await page.getByRole('link', { name: /Bài học 1: Register Page/ }).click(); | |
| const userRegistrationHeading = page.getByRole('heading', { name: 'User Registration' }); | |
| await expect(userRegistrationHeading).toBeVisible(); | |
| }); | |
| await test.step('Step 03: Fill register form then verify returned data', async () => { | |
| await page.getByLabel('Username').fill(registerData.username); | |
| await page.getByRole('textbox', { name: 'Email' }).fill(registerData.email); | |
| await page.getByRole('radio', { name: `${registerData.gender}`, exact: true }).check(); | |
| for (const hobby of registerData.hobbies) { | |
| await page.getByLabel(hobby, { exact: false }).check(); | |
| } | |
| await page.getByRole('listbox', { name: 'Interests' }).selectOption(registerData.interests); | |
| await page.getByRole('combobox', { name: 'Country' }).selectOption( | |
| { value: `${registerData.country}` } | |
| ); | |
| await page.getByLabel('Date of Birth').fill(registerData.dateOfBirth); | |
| await page.getByRole('button', { name: 'Profile Picture' }).setInputFiles( | |
| path.join(process.cwd(), 'tests', 'student-submissions', '01-nghia', 'lesson-02', 'mcp.jpeg') | |
| ); | |
| await page.getByLabel('Biography').fill(registerData.biography); | |
| await page.locator('#rating').fill(registerData.ratingUsOutOfTen); | |
| await page.locator('input[type="color"]').fill(registerData.favoriteColor); | |
| await page.getByText('Hover over me').hover({ timeout: 1_000 }); | |
| expect(page.locator('.tooltiptext', { hasText: 'Subscribe to our newsletter for updates' })).toBeVisible(); | |
| if (registerData.newsletter) { | |
| await page.locator('#newsletter').check(); | |
| } | |
| if (registerData.enableFeature) { | |
| await page.locator('#toggleOption + span').check(); | |
| } | |
| const box = await page.locator('#starRating').boundingBox(); | |
| if (box) { | |
| const x = box.x + (registerData.ratingStarOutOfFive / 5) * box.width; | |
| const y = box.y + box.height / 2; | |
| await page.mouse.click(x, y); | |
| } else { | |
| console.error('Could not get bounding box for #starRating'); | |
| } | |
| await page.locator('#customDate').evaluate((element, value) => element.setAttribute('value', value), registerData.customDate); | |
| await page.getByRole('button', { name: 'Register' }).click(); | |
| await expect(page.locator('#userTable tbody tr:first-child td:nth-child(2)')).toHaveText(registerData.username); | |
| await expect(page.locator('#userTable tbody tr:first-child td:nth-child(3)')).toHaveText(registerData.email); | |
| const infoCell = page.locator('#userTable tbody tr:first-child td:nth-child(4)'); | |
| await expect(infoCell).toContainText(`Gender: ${registerData.gender}`, { ignoreCase: true }); | |
| await expect(infoCell).toContainText(`Hobbies: ${registerData.hobbies.join(', ')}`); | |
| await expect(infoCell).toContainText(`Country: ${registerData.country}`); | |
| await expect(infoCell).toContainText(`Date of Birth: ${registerData.dateOfBirth}`); | |
| await expect(infoCell).toContainText(`Biography: ${registerData.biography}`); | |
| await expect(infoCell).toContainText(`Rating: ${registerData.ratingUsOutOfTen}`); | |
| await expect(infoCell).toContainText(`Favorite Color: ${registerData.favoriteColor}`); | |
| await expect(infoCell).toContainText(`Newsletter: ${registerData.newsletter ? 'Yes' : 'No'}`); | |
| await expect(infoCell).toContainText(`Enable Feature: ${registerData.enableFeature ? 'Yes' : 'No'}`); | |
| await expect(infoCell).toContainText(`Star Rating: ${Number(registerData.ratingStarOutOfFive.toFixed(1))}⭐`); | |
| await expect(infoCell).toContainText(`Custom Date: ${registerData.customDate}`); | |
| }); | |
| }); | |
| test('Test 02 - Product Page', async ({ page }) => { | |
| await test.step('Step 02: Click to "Bài học 2: Product page" then verify "Simple E-commerce" title display', async () => { | |
| await page.getByRole('link', { name: /Bài học 2: Product page/ }).click(); | |
| const simpleEcommerceHeading = page.getByRole('heading', { name: 'Simple E-commerce' }); | |
| await expect(simpleEcommerceHeading).toBeVisible(); | |
| }); | |
| await test.step('Step 03: Purchase products', async () => { | |
| for (const product of Object.values(shoppingCartData)) { | |
| for (let i = 0; i < product.quantity; i++) { | |
| //cách khác: await page.locator('.product-info').filter({ hasText: `${product.name}` }).getByRole('button', { name: 'Add to Cart' }).click(); | |
| await page.locator(`button[data-product-id="${product.id}"]`).click(); | |
| } | |
| } | |
| }); | |
| await test.step('Step 04: Verify shopping cart data', async () => { | |
| for (const product of Object.values(shoppingCartData)) { | |
| await verifyProductInCart(page, product.name, product.price, product.quantity); | |
| } | |
| }); | |
| await test.step('Step 05: Verify total price', async () => { | |
| const totalPrice = Object.values(shoppingCartData).reduce((total, product) => total + product.price * product.quantity, 0); | |
| await expect(page.locator('tfoot .total-price')).toHaveText(`$${totalPrice.toFixed(2)}`); | |
| }); | |
| }); | |
| test('Test 03 - Todo page', async ({ page }) => { | |
| page.on('dialog', dialog => dialog.accept()); | |
| const maxTodoCount = 100; | |
| // If maxTodoCount is odd, expectedEvenTodoCount = (maxTodoCount - 1) / 2 | |
| // e.g.: maxTodoCount = 13 => expectedEvenTodoCount = (13 - 1) / 2 = 6 | |
| const expectedEvenTodoCount = (maxTodoCount & 1) === 1 ? (maxTodoCount + 1) / 2 : maxTodoCount / 2; | |
| await test.step('Step 02: Click to "Bài học 3: Todo page" then verify "To-Do List" title display', async () => { | |
| await page.getByRole('link', { name: /Bài học 3: Todo page/ }).click(); | |
| const todoListHeading = page.getByRole('heading', { name: 'To-Do List' }); | |
| await expect(todoListHeading).toBeVisible(); | |
| }); | |
| await test.step(`Step 03: Add ${maxTodoCount} todos then verify there are ${maxTodoCount} todos`, async () => { | |
| for (let i = 1; i <= maxTodoCount; i++) { | |
| await page.locator('#new-task').fill(`todo ${i}`); | |
| await page.locator('#add-task').click(); | |
| } | |
| await expect(page.locator('#task-list li')).toHaveCount(maxTodoCount); | |
| }); | |
| await test.step(`Step 04: Remove all todo with odd index then verify there are ${expectedEvenTodoCount} todos`, async () => { | |
| for (let i = 1; i <= maxTodoCount; i += 2) { | |
| await page.locator(`#task-list li button#todo-${i}-delete`).click(); | |
| } | |
| await expect(page.locator('#task-list li')).toHaveCount(expectedEvenTodoCount); | |
| }); | |
| }); | |
| test('Test 04 - Personal notes', async ({ page }) => { | |
| const maxNoteCount = 10; | |
| await test.step('Step 02: Click to "Bài học 4: Personal notes" then verify "Personal Notes" title display', async () => { | |
| await page.getByRole('link', { name: /Bài học 4: Personal notes/ }).click(); | |
| const personalNotesHeading = page.getByRole('heading', { name: 'Personal Notes' }); | |
| await expect(personalNotesHeading).toBeVisible(); | |
| }); | |
| await test.step(`Step 03: Add ${maxNoteCount} notes then verify there are ${maxNoteCount} notes`, async () => { | |
| for (let i = 1; i <= maxNoteCount; i++) { | |
| await page.locator('#note-title').fill(`title ${i}`); | |
| await page.locator('#note-content').fill(faker.lorem.sentence()); | |
| await page.locator('#add-note').click(); | |
| } | |
| await expect(page.locator('#notes-list li')).toHaveCount(maxNoteCount); | |
| }); | |
| await test.step('Step 04: Search the added note', async () => { | |
| const searchValue = `title ${faker.number.int({ min: 1, max: maxNoteCount })}`; | |
| await page.locator('#search').fill(searchValue); | |
| await expect(page.locator('#notes-list li', { hasText: searchValue }).first()).toBeVisible(); | |
| }); | |
| }); | |
| async function verifyProductInCart(page: Page, productName: string, expectedPrice: number, expectedQuantity: number) { | |
| const productRow = page.locator('tbody#cart-items tr').filter({ hasText: productName }); | |
| await expect(productRow.locator('td:nth-child(1)')).toHaveText(productName); | |
| await expect(productRow.locator('td:nth-child(2)')).toHaveText(`$${expectedPrice.toFixed(2)}`); | |
| await expect(productRow.locator('td:nth-child(3)')).toHaveText(expectedQuantity.toString()); | |
| await expect(productRow.locator('td:nth-child(4)')).toHaveText(`$${(expectedPrice * expectedQuantity).toFixed(2)}`); | |
| } | |
| }); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment