| import type { Page } from 'playwright'; |
| import { logger } from '../../utils/logger'; |
| import { SELECTORS } from '../selectors'; |
| import { acceptEulaEverywhere } from './login'; |
|
|
| export interface DropClassResult { |
| attempted: boolean; |
| dropped: boolean; |
| reason?: string; |
| } |
|
|
| |
| |
| |
| |
| |
| export async function dropClassByTitle( |
| page: Page, |
| classTitle: string, |
| ): Promise<DropClassResult> { |
| if (!classTitle || classTitle.trim() === '') { |
| return { |
| attempted: false, |
| dropped: false, |
| reason: 'Class title is empty', |
| }; |
| } |
|
|
| logger.info('Attempting to drop quota-limited class', { classTitle }); |
|
|
| await page.goto('https://www.turnitin.com/s_home.asp?lang=en_us', { |
| waitUntil: 'domcontentloaded', |
| timeout: 60000, |
| }); |
| await page.waitForTimeout(1500); |
| await acceptEulaEverywhere(page); |
|
|
| const classLink = page |
| .locator(SELECTORS.class.classNameLink) |
| .filter({ hasText: classTitle }) |
| .first(); |
|
|
| if (!(await classLink.isVisible({ timeout: 10000 }).catch(() => false))) { |
| logger.info('Class is already absent from Turnitin home', { classTitle }); |
| return { |
| attempted: true, |
| dropped: true, |
| reason: 'Class already absent', |
| }; |
| } |
|
|
| const classRow = page.locator('tr').filter({ hasText: classTitle }).first(); |
| const deleteLink = classRow |
| .locator('td.class_delete a, a[href*="confirmDropClass"]') |
| .first(); |
|
|
| if (!(await deleteLink.isVisible({ timeout: 5000 }).catch(() => false))) { |
| logger.warn('Class delete link was not found', { classTitle }); |
| return { |
| attempted: true, |
| dropped: false, |
| reason: 'Delete link not found', |
| }; |
| } |
|
|
| let dialogAccepted = false; |
| const dialogHandler = async (dialog: any) => { |
| dialogAccepted = true; |
| await dialog.accept().catch(() => {}); |
| }; |
|
|
| page.once('dialog', dialogHandler); |
| await Promise.all([ |
| page |
| .waitForNavigation({ waitUntil: 'domcontentloaded', timeout: 30000 }) |
| .catch(() => null), |
| deleteLink.click({ force: true }), |
| ]); |
| await page.waitForTimeout(2500); |
|
|
| if (!dialogAccepted) { |
| logger.warn('Drop class confirmation dialog was not observed', { |
| classTitle, |
| }); |
| } |
|
|
| await page.goto('https://www.turnitin.com/s_home.asp?lang=en_us', { |
| waitUntil: 'domcontentloaded', |
| timeout: 60000, |
| }); |
| await page.waitForTimeout(1500); |
|
|
| const stillVisible = await page |
| .locator(SELECTORS.class.classNameLink) |
| .filter({ hasText: classTitle }) |
| .first() |
| .isVisible({ timeout: 5000 }) |
| .catch(() => false); |
|
|
| if (stillVisible) { |
| logger.warn('Class still visible after delete attempt', { classTitle }); |
| return { |
| attempted: true, |
| dropped: false, |
| reason: 'Class still visible after delete', |
| }; |
| } |
|
|
| logger.info('Quota-limited class dropped successfully', { classTitle }); |
| return { attempted: true, dropped: true }; |
| } |
|
|