From bcbe949ee701dfb1165172206be910d8fe1d96a4 Mon Sep 17 00:00:00 2001 From: G201021 Date: Thu, 24 Oct 2024 18:28:27 -0400 Subject: [PATCH] content reduction deletion of older versions of the script --- .github/workflows/playwright.yml | 27 ++ .gitignore | 5 + example8.js | 1 + package-lock.json | 91 ++++++ package.json | 14 + playwright.config.js | 79 +++++ tests-examples/demo-todo-app.spec.js | 449 +++++++++++++++++++++++++++ tests/OrangeHRM_Title.spec.js | 12 + tests/OrangeHRM_URL.spec.js | 8 + tests/example.png | Bin 0 -> 15611 bytes tests/example12.js | 54 ++++ tests/test.js | 8 + 12 files changed, 748 insertions(+) create mode 100644 .github/workflows/playwright.yml create mode 100644 .gitignore create mode 100644 example8.js create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 playwright.config.js create mode 100644 tests-examples/demo-todo-app.spec.js create mode 100644 tests/OrangeHRM_Title.spec.js create mode 100644 tests/OrangeHRM_URL.spec.js create mode 100644 tests/example.png create mode 100644 tests/example12.js create mode 100644 tests/test.js diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml new file mode 100644 index 0000000..467190b --- /dev/null +++ b/.github/workflows/playwright.yml @@ -0,0 +1,27 @@ +name: Playwright Tests +on: + push: + branches: [ main, master ] + pull_request: + branches: [ main, master ] +jobs: + test: + timeout-minutes: 60 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: lts/* + - name: Install dependencies + run: npm ci + - name: Install Playwright Browsers + run: npx playwright install --with-deps + - name: Run Playwright tests + run: npx playwright test + - uses: actions/upload-artifact@v4 + if: always() + with: + name: playwright-report + path: playwright-report/ + retention-days: 30 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..68c5d18 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +node_modules/ +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/example8.js b/example8.js new file mode 100644 index 0000000..9ad737e --- /dev/null +++ b/example8.js @@ -0,0 +1 @@ +console.log("hello G"); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..f018b63 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,91 @@ +{ + "name": "playwright-npm", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "playwright-npm", + "version": "1.0.0", + "license": "ISC", + "devDependencies": { + "@playwright/test": "^1.46.0", + "@types/node": "^22.1.0" + } + }, + "node_modules/@playwright/test": { + "version": "1.46.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.46.0.tgz", + "integrity": "sha512-/QYft5VArOrGRP5pgkrfKksqsKA6CEFyGQ/gjNe6q0y4tZ1aaPfq4gIjudr1s3D+pXyrPRdsy4opKDrjBabE5w==", + "dev": true, + "dependencies": { + "playwright": "1.46.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@types/node": { + "version": "22.1.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.1.0.tgz", + "integrity": "sha512-AOmuRF0R2/5j1knA3c6G3HOk523Ga+l+ZXltX8SF1+5oqcXijjfTd8fY3XRZqSihEu9XhtQnKYLmkFaoxgsJHw==", + "dev": true, + "dependencies": { + "undici-types": "~6.13.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/playwright": { + "version": "1.46.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.46.0.tgz", + "integrity": "sha512-XYJ5WvfefWONh1uPAUAi0H2xXV5S3vrtcnXe6uAOgdGi3aSpqOSXX08IAjXW34xitfuOJsvXU5anXZxPSEQiJw==", + "dev": true, + "dependencies": { + "playwright-core": "1.46.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.46.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.46.0.tgz", + "integrity": "sha512-9Y/d5UIwuJk8t3+lhmMSAJyNP1BUC/DqP3cQJDQQL/oWqAiuPTLgy7Q5dzglmTLwcBRdetzgNM/gni7ckfTr6A==", + "dev": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/undici-types": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.13.0.tgz", + "integrity": "sha512-xtFJHudx8S2DSoujjMd1WeWvn7KKWFRESZTMeL1RptAYERu29D6jphMjjY+vn96jvN3kVPDNxU/E13VTaXj6jg==", + "dev": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..8f9a15b --- /dev/null +++ b/package.json @@ -0,0 +1,14 @@ +{ + "name": "playwright-npm", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": {}, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "@playwright/test": "^1.46.0", + "@types/node": "^22.1.0" + } +} diff --git a/playwright.config.js b/playwright.config.js new file mode 100644 index 0000000..4208666 --- /dev/null +++ b/playwright.config.js @@ -0,0 +1,79 @@ +// @ts-check +const { defineConfig, devices } = require('@playwright/test'); + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// require('dotenv').config({ path: path.resolve(__dirname, '.env') }); + +/** + * @see https://playwright.dev/docs/test-configuration + */ +module.exports = defineConfig({ + testDir: './tests', + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'html', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://127.0.0.1:3000', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }, + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], + + /* Run your local dev server before starting the tests */ + // webServer: { + // command: 'npm run start', + // url: 'http://127.0.0.1:3000', + // reuseExistingServer: !process.env.CI, + // }, +}); + diff --git a/tests-examples/demo-todo-app.spec.js b/tests-examples/demo-todo-app.spec.js new file mode 100644 index 0000000..e2eb87c --- /dev/null +++ b/tests-examples/demo-todo-app.spec.js @@ -0,0 +1,449 @@ +// @ts-check +const { test, expect } = require('@playwright/test'); + +test.beforeEach(async ({ page }) => { + await page.goto('https://demo.playwright.dev/todomvc'); +}); + +const TODO_ITEMS = [ + 'buy some cheese', + 'feed the cat', + 'book a doctors appointment' +]; + +test.describe('New Todo', () => { + test('should allow me to add todo items', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create 1st todo. + await newTodo.fill(TODO_ITEMS[0]); + await newTodo.press('Enter'); + + // Make sure the list only has one todo item. + await expect(page.getByTestId('todo-title')).toHaveText([ + TODO_ITEMS[0] + ]); + + // Create 2nd todo. + await newTodo.fill(TODO_ITEMS[1]); + await newTodo.press('Enter'); + + // Make sure the list now has two todo items. + await expect(page.getByTestId('todo-title')).toHaveText([ + TODO_ITEMS[0], + TODO_ITEMS[1] + ]); + + await checkNumberOfTodosInLocalStorage(page, 2); + }); + + test('should clear text input field when an item is added', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create one todo item. + await newTodo.fill(TODO_ITEMS[0]); + await newTodo.press('Enter'); + + // Check that input is empty. + await expect(newTodo).toBeEmpty(); + await checkNumberOfTodosInLocalStorage(page, 1); + }); + + test('should append new items to the bottom of the list', async ({ page }) => { + // Create 3 items. + await createDefaultTodos(page); + + // create a todo count locator + const todoCount = page.getByTestId('todo-count') + + // Check test using different methods. + await expect(page.getByText('3 items left')).toBeVisible(); + await expect(todoCount).toHaveText('3 items left'); + await expect(todoCount).toContainText('3'); + await expect(todoCount).toHaveText(/3/); + + // Check all items in one call. + await expect(page.getByTestId('todo-title')).toHaveText(TODO_ITEMS); + await checkNumberOfTodosInLocalStorage(page, 3); + }); +}); + +test.describe('Mark all as completed', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test.afterEach(async ({ page }) => { + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test('should allow me to mark all items as completed', async ({ page }) => { + // Complete all todos. + await page.getByLabel('Mark all as complete').check(); + + // Ensure all todos have 'completed' class. + await expect(page.getByTestId('todo-item')).toHaveClass(['completed', 'completed', 'completed']); + await checkNumberOfCompletedTodosInLocalStorage(page, 3); + }); + + test('should allow me to clear the complete state of all items', async ({ page }) => { + const toggleAll = page.getByLabel('Mark all as complete'); + // Check and then immediately uncheck. + await toggleAll.check(); + await toggleAll.uncheck(); + + // Should be no completed classes. + await expect(page.getByTestId('todo-item')).toHaveClass(['', '', '']); + }); + + test('complete all checkbox should update state when items are completed / cleared', async ({ page }) => { + const toggleAll = page.getByLabel('Mark all as complete'); + await toggleAll.check(); + await expect(toggleAll).toBeChecked(); + await checkNumberOfCompletedTodosInLocalStorage(page, 3); + + // Uncheck first todo. + const firstTodo = page.getByTestId('todo-item').nth(0); + await firstTodo.getByRole('checkbox').uncheck(); + + // Reuse toggleAll locator and make sure its not checked. + await expect(toggleAll).not.toBeChecked(); + + await firstTodo.getByRole('checkbox').check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 3); + + // Assert the toggle all is checked again. + await expect(toggleAll).toBeChecked(); + }); +}); + +test.describe('Item', () => { + + test('should allow me to mark items as complete', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create two items. + for (const item of TODO_ITEMS.slice(0, 2)) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } + + // Check first item. + const firstTodo = page.getByTestId('todo-item').nth(0); + await firstTodo.getByRole('checkbox').check(); + await expect(firstTodo).toHaveClass('completed'); + + // Check second item. + const secondTodo = page.getByTestId('todo-item').nth(1); + await expect(secondTodo).not.toHaveClass('completed'); + await secondTodo.getByRole('checkbox').check(); + + // Assert completed class. + await expect(firstTodo).toHaveClass('completed'); + await expect(secondTodo).toHaveClass('completed'); + }); + + test('should allow me to un-mark items as complete', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create two items. + for (const item of TODO_ITEMS.slice(0, 2)) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } + + const firstTodo = page.getByTestId('todo-item').nth(0); + const secondTodo = page.getByTestId('todo-item').nth(1); + const firstTodoCheckbox = firstTodo.getByRole('checkbox'); + + await firstTodoCheckbox.check(); + await expect(firstTodo).toHaveClass('completed'); + await expect(secondTodo).not.toHaveClass('completed'); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + + await firstTodoCheckbox.uncheck(); + await expect(firstTodo).not.toHaveClass('completed'); + await expect(secondTodo).not.toHaveClass('completed'); + await checkNumberOfCompletedTodosInLocalStorage(page, 0); + }); + + test('should allow me to edit an item', async ({ page }) => { + await createDefaultTodos(page); + + const todoItems = page.getByTestId('todo-item'); + const secondTodo = todoItems.nth(1); + await secondTodo.dblclick(); + await expect(secondTodo.getByRole('textbox', { name: 'Edit' })).toHaveValue(TODO_ITEMS[1]); + await secondTodo.getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); + await secondTodo.getByRole('textbox', { name: 'Edit' }).press('Enter'); + + // Explicitly assert the new text value. + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + 'buy some sausages', + TODO_ITEMS[2] + ]); + await checkTodosInLocalStorage(page, 'buy some sausages'); + }); +}); + +test.describe('Editing', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test('should hide other controls when editing', async ({ page }) => { + const todoItem = page.getByTestId('todo-item').nth(1); + await todoItem.dblclick(); + await expect(todoItem.getByRole('checkbox')).not.toBeVisible(); + await expect(todoItem.locator('label', { + hasText: TODO_ITEMS[1], + })).not.toBeVisible(); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test('should save edits on blur', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).dispatchEvent('blur'); + + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + 'buy some sausages', + TODO_ITEMS[2], + ]); + await checkTodosInLocalStorage(page, 'buy some sausages'); + }); + + test('should trim entered text', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill(' buy some sausages '); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Enter'); + + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + 'buy some sausages', + TODO_ITEMS[2], + ]); + await checkTodosInLocalStorage(page, 'buy some sausages'); + }); + + test('should remove the item if an empty text string was entered', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill(''); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Enter'); + + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + TODO_ITEMS[2], + ]); + }); + + test('should cancel edits on escape', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Escape'); + await expect(todoItems).toHaveText(TODO_ITEMS); + }); +}); + +test.describe('Counter', () => { + test('should display the current number of todo items', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // create a todo count locator + const todoCount = page.getByTestId('todo-count') + + await newTodo.fill(TODO_ITEMS[0]); + await newTodo.press('Enter'); + await expect(todoCount).toContainText('1'); + + await newTodo.fill(TODO_ITEMS[1]); + await newTodo.press('Enter'); + await expect(todoCount).toContainText('2'); + + await checkNumberOfTodosInLocalStorage(page, 2); + }); +}); + +test.describe('Clear completed button', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + }); + + test('should display the correct text', async ({ page }) => { + await page.locator('.todo-list li .toggle').first().check(); + await expect(page.getByRole('button', { name: 'Clear completed' })).toBeVisible(); + }); + + test('should remove completed items when clicked', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).getByRole('checkbox').check(); + await page.getByRole('button', { name: 'Clear completed' }).click(); + await expect(todoItems).toHaveCount(2); + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); + }); + + test('should be hidden when there are no items that are completed', async ({ page }) => { + await page.locator('.todo-list li .toggle').first().check(); + await page.getByRole('button', { name: 'Clear completed' }).click(); + await expect(page.getByRole('button', { name: 'Clear completed' })).toBeHidden(); + }); +}); + +test.describe('Persistence', () => { + test('should persist its data', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + for (const item of TODO_ITEMS.slice(0, 2)) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } + + const todoItems = page.getByTestId('todo-item'); + const firstTodoCheck = todoItems.nth(0).getByRole('checkbox'); + await firstTodoCheck.check(); + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); + await expect(firstTodoCheck).toBeChecked(); + await expect(todoItems).toHaveClass(['completed', '']); + + // Ensure there is 1 completed item. + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + + // Now reload. + await page.reload(); + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); + await expect(firstTodoCheck).toBeChecked(); + await expect(todoItems).toHaveClass(['completed', '']); + }); +}); + +test.describe('Routing', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + // make sure the app had a chance to save updated todos in storage + // before navigating to a new view, otherwise the items can get lost :( + // in some frameworks like Durandal + await checkTodosInLocalStorage(page, TODO_ITEMS[0]); + }); + + test('should allow me to display active items', async ({ page }) => { + const todoItem = page.getByTestId('todo-item'); + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + await page.getByRole('link', { name: 'Active' }).click(); + await expect(todoItem).toHaveCount(2); + await expect(todoItem).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); + }); + + test('should respect the back button', async ({ page }) => { + const todoItem = page.getByTestId('todo-item'); + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + + await test.step('Showing all items', async () => { + await page.getByRole('link', { name: 'All' }).click(); + await expect(todoItem).toHaveCount(3); + }); + + await test.step('Showing active items', async () => { + await page.getByRole('link', { name: 'Active' }).click(); + }); + + await test.step('Showing completed items', async () => { + await page.getByRole('link', { name: 'Completed' }).click(); + }); + + await expect(todoItem).toHaveCount(1); + await page.goBack(); + await expect(todoItem).toHaveCount(2); + await page.goBack(); + await expect(todoItem).toHaveCount(3); + }); + + test('should allow me to display completed items', async ({ page }) => { + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + await page.getByRole('link', { name: 'Completed' }).click(); + await expect(page.getByTestId('todo-item')).toHaveCount(1); + }); + + test('should allow me to display all items', async ({ page }) => { + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + await page.getByRole('link', { name: 'Active' }).click(); + await page.getByRole('link', { name: 'Completed' }).click(); + await page.getByRole('link', { name: 'All' }).click(); + await expect(page.getByTestId('todo-item')).toHaveCount(3); + }); + + test('should highlight the currently applied filter', async ({ page }) => { + await expect(page.getByRole('link', { name: 'All' })).toHaveClass('selected'); + + //create locators for active and completed links + const activeLink = page.getByRole('link', { name: 'Active' }); + const completedLink = page.getByRole('link', { name: 'Completed' }); + await activeLink.click(); + + // Page change - active items. + await expect(activeLink).toHaveClass('selected'); + await completedLink.click(); + + // Page change - completed items. + await expect(completedLink).toHaveClass('selected'); + }); +}); + +async function createDefaultTodos(page) { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + for (const item of TODO_ITEMS) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } +} + +/** + * @param {import('@playwright/test').Page} page + * @param {number} expected + */ + async function checkNumberOfTodosInLocalStorage(page, expected) { + return await page.waitForFunction(e => { + return JSON.parse(localStorage['react-todos']).length === e; + }, expected); +} + +/** + * @param {import('@playwright/test').Page} page + * @param {number} expected + */ + async function checkNumberOfCompletedTodosInLocalStorage(page, expected) { + return await page.waitForFunction(e => { + return JSON.parse(localStorage['react-todos']).filter(i => i.completed).length === e; + }, expected); +} + +/** + * @param {import('@playwright/test').Page} page + * @param {string} title + */ +async function checkTodosInLocalStorage(page, title) { + return await page.waitForFunction(t => { + return JSON.parse(localStorage['react-todos']).map(i => i.title).includes(t); + }, title); +} diff --git a/tests/OrangeHRM_Title.spec.js b/tests/OrangeHRM_Title.spec.js new file mode 100644 index 0000000..c499a7e --- /dev/null +++ b/tests/OrangeHRM_Title.spec.js @@ -0,0 +1,12 @@ +// @ts-check + +const { test, expect } = require("@playwright/test"); + +test("Validate Orange HRM Website title", async ({ page }) => { + await page.goto("https://opensource-demo.orangehrmlive.com/web/index.php/auth/login"); + await expect(page).toHaveTitle(/OrangeHRM/); + + +}); + + diff --git a/tests/OrangeHRM_URL.spec.js b/tests/OrangeHRM_URL.spec.js new file mode 100644 index 0000000..2ad10db --- /dev/null +++ b/tests/OrangeHRM_URL.spec.js @@ -0,0 +1,8 @@ +// @ts-check + +const { test, expect } = require("@playwright/test"); + +test("Validate Orange HRM Website title", async ({ page }) => { + await page.goto("https://opensource-demo.orangehrmlive.com/"); + await expect(page).toHaveURL(/.*orangehrmlive/); +}); diff --git a/tests/example.png b/tests/example.png new file mode 100644 index 0000000000000000000000000000000000000000..0143ec0e8ec64ea7eae3acca72de606e17989190 GIT binary patch literal 15611 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 literal 0 HcmV?d00001 diff --git a/tests/example12.js b/tests/example12.js new file mode 100644 index 0000000..305e16a --- /dev/null +++ b/tests/example12.js @@ -0,0 +1,54 @@ +const { chromium } = require("playwright"); // Import the Playwright library, specifically for Chromium. + +async function sortHackerNewsArticles() { + // Launch the browser in non-headless mode (headless: false means you'll see the browser). + const browser = await chromium.launch({ headless: false }); + const context = await browser.newContext(); // Create a new browser context (like a new session). + const page = await context.newPage(); // Open a new tab or page in the browser. + + // Define the list of URLs to visit (these are the "newest" pages on Hacker News). + const links = [ + "https://news.ycombinator.com/newest", + "https://news.ycombinator.com/newest?next=41213281&n=31", + "https://news.ycombinator.com/newest?next=41212570&n=61", + "https://news.ycombinator.com/newest?next=41212118&n=91", + ]; + + let listDates = []; // Initialize an empty array to store the dates. + + // Loop through each URL in the "links" array. + for (const i of links) { + await page.goto(i); // Navigate to the current link. + await page.waitForSelector("td.subtext span.age"); // Wait until the "age" element is present on the page. + + // Extract the "title" attribute from all elements matching "td.subtext span.age". + const dates = await page.evaluate(() => { + const elements = Array.from( + document.querySelectorAll("td.subtext span.age") + ); + return elements + .map((el) => el.getAttribute("title")) // Get the "title" attribute, which contains the date and time. + .filter((date) => date); // Filter out any null or undefined values (though unlikely here). + }); + + listDates = listDates.concat(dates); // Concatenate the extracted dates into the "listDates" array. + } + + listDates = listDates.slice(0, 100); // Ensure that only the first 100 dates are kept. + + // Sort the dates from newest to oldest. + listDates.sort((a, b) => new Date(b) - new Date(a)); + + console.log(listDates); // Output the sorted list of dates to the console. + + // Check if the dates are correctly sorted from newest to oldest. + const isSorted = listDates.every( + (date, i) => i === 0 || new Date(date) <= new Date(listDates[i - 1]) + ); + + await browser.close(); // Close the browser once done. +} + +(async () => { + await sortHackerNewsArticles(); // Call the function to run the script. +})(); \ No newline at end of file diff --git a/tests/test.js b/tests/test.js new file mode 100644 index 0000000..d549b0c --- /dev/null +++ b/tests/test.js @@ -0,0 +1,8 @@ +// Create a new Date object with the given date +const dateString = "8/26/1995"; +const date = new Date(dateString); + +// Get the milliseconds since the Unix epoch +const milliseconds = date.getTime(); + +console.log(milliseconds);