diff --git a/.idea/sonarlint/issuestore/index.pb b/.idea/sonarlint/issuestore/index.pb new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/securityhotspotstore/index.pb b/.idea/sonarlint/securityhotspotstore/index.pb new file mode 100644 index 0000000..e69de29 diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/1.png b/1.png deleted file mode 100644 index 8eab790..0000000 Binary files a/1.png and /dev/null differ diff --git a/2.png b/2.png deleted file mode 100644 index 3e0fe80..0000000 Binary files a/2.png and /dev/null differ diff --git a/error.png b/error.png deleted file mode 100644 index 8c03c9d..0000000 Binary files a/error.png and /dev/null differ diff --git a/i.png b/i.png deleted file mode 100644 index 03b0f67..0000000 Binary files a/i.png and /dev/null differ diff --git a/package.json b/package.json index 66dce37..8a7a8e4 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,9 @@ "typescript": "^4.9.5" }, "dependencies": { + "@types/node-cron": "^3.0.7", "dotenv": "^16.0.3", + "node-cron": "^3.0.2", "playwright": "^1.31.2", "telegraf": "^4.12.2" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 24d12d1..28a803c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3,6 +3,7 @@ lockfileVersion: 5.4 specifiers: '@playwright/test': ^1.31.2 '@types/node': ^18.15.1 + '@types/node-cron': ^3.0.7 '@typescript-eslint/eslint-plugin': ^5.0.0 dotenv: ^16.0.3 eslint: ^8.36.0 @@ -10,6 +11,7 @@ specifiers: eslint-plugin-import: ^2.25.2 eslint-plugin-n: ^15.0.0 eslint-plugin-promise: ^6.0.0 + node-cron: ^3.0.2 nodemon: ^2.0.21 playwright: ^1.31.2 telegraf: ^4.12.2 @@ -17,7 +19,9 @@ specifiers: typescript: ^4.9.5 dependencies: + '@types/node-cron': 3.0.7 dotenv: 16.0.3 + node-cron: 3.0.2 playwright: 1.31.2 telegraf: 4.12.2 @@ -172,6 +176,10 @@ packages: resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} dev: true + /@types/node-cron/3.0.7: + resolution: {integrity: sha512-9PuLtBboc/+JJ7FshmJWv769gDonTpItN0Ol5TMwclpSQNjVyB2SRxSKBcTtbSysSL5R7Oea06kTTFNciCoYwA==} + dev: false + /@types/node/18.15.1: resolution: {integrity: sha512-U2TWca8AeHSmbpi314QBESRk7oPjSZjDsR+c+H4ECC1l+kFgpZf8Ydhv3SJpPy51VyZHHqxlb6mTTqYNNRVAIw==} dev: true @@ -1451,6 +1459,13 @@ packages: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} dev: true + /node-cron/3.0.2: + resolution: {integrity: sha512-iP8l0yGlNpE0e6q1o185yOApANRe47UPbLf4YxfbiNHt/RU5eBcGB/e0oudruheSf+LQeDMezqC5BVAb5wwRcQ==} + engines: {node: '>=6.0.0'} + dependencies: + uuid: 8.3.2 + dev: false + /node-fetch/2.6.9: resolution: {integrity: sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==} engines: {node: 4.x || >=6.0.0} @@ -1956,6 +1971,11 @@ packages: punycode: 2.3.0 dev: true + /uuid/8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + hasBin: true + dev: false + /v8-compile-cache-lib/3.0.1: resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} dev: true diff --git a/src/index.ts b/src/index.ts index 5a0466f..39efb22 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,25 +1,41 @@ import { chromium, type Page } from 'playwright' -import { expect } from '@playwright/test' -import { Markup, Telegraf, Telegram } from 'telegraf' +import { type Browser, expect } from '@playwright/test' +import { Markup, Telegraf } from 'telegraf' import dotenv from 'dotenv' import { type InputFile } from 'telegraf/types' +import { message } from 'telegraf/filters' +import cron from 'node-cron' + dotenv.config() const url = 'https://icp.administracionelectronica.gob.es/icpplustiem/citar?p=28&locale=es&appkey=null' -const nie = 'y4149706z' -const name = 'ANDRII ZADOROZHNYI' -const countryCode = '152' +const { NIE, NAME, COUNTRY_CODE } = process.env +const NO_APPOINTMENT = 'no hay citas disponibles' const delay = async (ms: number) => await new Promise(resolve => setTimeout(resolve, ms)) +const sendScreenshot = async (bot: Telegraf, page: Page, message?: string) => { + const screenshot = await page.screenshot() + const inputFile: InputFile = { + source: screenshot, + filename: 'screenshot.png' + } + await bot.telegram.sendDocument(CHAT_ID, inputFile, { caption: message }) +} +const sendText = async (bot: Telegraf, text: string) => { + await bot.telegram.sendMessage(CHAT_ID, text) +} const CHAT_ID = process.env.CHAT_ID ?? '' const bot = new Telegraf(process?.env.BOT_TOKEN ?? '') -console.log(process.env.BOT_TOKEN) - +const MENU = { + SCRAPE_NOW: 'Scrape now', + START_TIMER: 'Start timer', + STOP_TIMER: 'Stop timer' +} as const bot.start(async (ctx) => { - console.log(ctx) - console.log(ctx.chat.id) - await ctx.reply('this is text', Markup + await ctx.reply(`chat id: ${ctx.chat.id}`, Markup .keyboard([ - ['Scrape', 'button 2'] // Row1 with 2 buttons + [MENU.SCRAPE_NOW], + [MENU.START_TIMER], + [MENU.STOP_TIMER] ]) .oneTime() .resize() @@ -27,63 +43,100 @@ bot.start(async (ctx) => { }) const step1 = async (page: Page) => { await page.goto(url) - await page.screenshot({ path: 'i.png' }) + await sendScreenshot(bot, page, 'step 1') } const step2 = async (page: Page) => { const select = page.getByLabel('TRÁMITES CUERPO NACIONAL DE POLICÍA') try { await expect(select).toBeEnabled() await select.selectOption(['4010']) + + await delay(2000) + await page.click('#btnAceptar') - await page.click('#btnEntrar') + await sendScreenshot(bot, page, 'step 2') } catch (e) { - throw new Error('select is not enabled') + await sendScreenshot(bot, page, 'select is not enabled') + await sendText(bot, 'select is not enabled, ' + JSON.stringify(e)) + await page.close() } } -const step3 = async (page: Page) => { - await page.locator('#txtIdCitado').fill(nie) - await page.locator('#txtDesCitado').fill(name) - await page.locator('#txtPaisNac').selectOption([countryCode]) - await delay(2000) - await page.screenshot({ path: '1.png' }) - await delay(2000) - await page.click('#btnEnviar') - await page.screenshot({ path: '2.png' }) +const step3 = async (page: Page) => { + await page.click('#btnEntrar') + await sendScreenshot(bot, page, 'step 3') } const step4 = async (page: Page) => { + await page.locator('#txtIdCitado').fill(NIE ?? '') + await page.locator('#txtDesCitado').fill(NAME ?? '') + await page.locator('#txtPaisNac').selectOption([COUNTRY_CODE ?? '']) + await delay(2000) + await sendScreenshot(bot, page, 'filled form') + await page.click('#btnEnviar') + await sendScreenshot(bot, page, 'step 4') +} + +const step5 = async (page: Page, browser: Browser) => { try { + if (await page.getByText(NO_APPOINTMENT).count() > 0) { + await sendScreenshot(bot, page, 'step 5 failed') + await sendText(bot, 'No hay citas disponibles') + await page.close() + await browser.close() + return + } + await delay(2000) const button = await page.waitForSelector('#btnEnviar', { timeout: 1000 }) await button.click() + await sendScreenshot(bot, page, 'step 5') } catch (e) { - const errorScreenshot = await page.screenshot({ path: 'error.png' }) - await bot.telegram.sendMessage(CHAT_ID, 'No hay citas disponibles') - const inputFile: InputFile = { - source: errorScreenshot, - filename: 'error.png' - } - await bot.telegram.sendPhoto(CHAT_ID, inputFile) - throw new Error('No hay citas disponibles') + await sendScreenshot(bot, page, 'step 5') + await sendText(bot, 'No hay citas disponibles') } - await page.click('#btnEnviar') - await page.screenshot({ path: '3.png' }) } +const step6 = async (page: Page) => { + await delay(2000) + await page.click('#btnEnviar') + await sendScreenshot(bot, page, 'step 6') +} + const scrape = async () => { const browser = await chromium.launch({ headless: false }) - - const page = await browser.newPage() - await step1(page) - await step2(page) - await step3(page) - await step4(page) - await browser.close() + try { + const page = await browser.newPage() + await step1(page) + await step2(page) + await step3(page) + await step4(page) + await step5(page, browser) + await step6(page) + await browser.close() + } catch (e) { + await sendText(bot, JSON.stringify(e)) + await browser?.close?.() + } } -bot.on('message', async (ctx) => { - // @ts-expect-error - if (ctx.message?.text === 'Scrape') { +const cronTask = cron.schedule('*/1 * * * *', async () => { + await scrape() +}) +console.log('valid: ', cron.validate('*/1 * * * *')) +bot.on(message('text'), async (ctx) => { + const text = ctx.message?.text + if (ctx.message.from.id !== Number(process.env.MY_TELEGRAM_ID)) return + + if (text === MENU.SCRAPE_NOW) { await scrape() } + if (text === MENU.START_TIMER) { + await cronTask.start() + await sendText(bot, 'Timer started') + } + if (text === MENU.STOP_TIMER) { + await cronTask.stop() + await sendText(bot, 'Timer stopped') + } }) + void bot.launch() process.once('SIGINT', () => { bot.stop('SIGINT') }) diff --git a/tests-examples/demo-todo-app.spec.ts b/tests-examples/demo-todo-app.spec.ts deleted file mode 100644 index 2fd6016..0000000 --- a/tests-examples/demo-todo-app.spec.ts +++ /dev/null @@ -1,437 +0,0 @@ -import { test, expect, type Page } from '@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: 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'); - } -} - -async function checkNumberOfTodosInLocalStorage(page: Page, expected: number) { - return await page.waitForFunction(e => { - return JSON.parse(localStorage['react-todos']).length === e; - }, expected); -} - -async function checkNumberOfCompletedTodosInLocalStorage(page: Page, expected: number) { - return await page.waitForFunction(e => { - return JSON.parse(localStorage['react-todos']).filter((todo: any) => todo.completed).length === e; - }, expected); -} - -async function checkTodosInLocalStorage(page: Page, title: string) { - return await page.waitForFunction(t => { - return JSON.parse(localStorage['react-todos']).map((todo: any) => todo.title).includes(t); - }, title); -} diff --git a/tests/example.spec.ts b/tests/example.spec.ts deleted file mode 100644 index c511525..0000000 --- a/tests/example.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { test, expect } from '@playwright/test'; - -test('has title', async ({ page }) => { - await page.goto('https://playwright.dev/'); - - // Expect a title "to contain" a substring. - await expect(page).toHaveTitle(/Playwright/); -}); - -test('get started link', async ({ page }) => { - await page.goto('https://playwright.dev/'); - - // Click the get started link. - await page.getByRole('link', { name: 'Get started' }).click(); - - // Expects the URL to contain intro. - await expect(page).toHaveURL(/.*intro/); -});