relv-space3 / src /engine /steps /class-management.ts
relv-dev's picture
Deploy latest verified Turnitin worker
9e212e3 verified
Raw
History Blame Contribute Delete
3.08 kB
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;
}
/**
* Drop a class from the Turnitin student home page. This is used only after a
* hard submission quota limit so exhausted accounts do not keep showing stale
* class entries during future quota scans.
*/
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 };
}