| import type { Page, BrowserContext, Frame, Locator } from 'playwright'; |
| import { logger } from '../../utils/logger'; |
| import { |
| SELECTORS, |
| deepClickByText, |
| resolveAssignmentScope, |
| } from '../selectors'; |
|
|
| type ViewerScope = Page | Frame; |
|
|
| function ownerPage(scope: ViewerScope): Page { |
| return typeof (scope as any).page === 'function' |
| ? (scope as Frame).page() |
| : (scope as Page); |
| } |
|
|
| function isReportSubmissionUrl(url: string): boolean { |
| return ( |
| url.includes('reports.integrity.turnitin.com') && |
| /submission-viewer|\/submission\//i.test(url) |
| ); |
| } |
|
|
| function findReportViewerScope(context: BrowserContext): ViewerScope | null { |
| for (const candidatePage of context.pages()) { |
| if (isReportSubmissionUrl(candidatePage.url())) { |
| return candidatePage; |
| } |
|
|
| const reportFrame = candidatePage |
| .frames() |
| .find((frame) => isReportSubmissionUrl(frame.url())); |
| if (reportFrame) { |
| return reportFrame; |
| } |
| } |
|
|
| return null; |
| } |
|
|
| |
| |
| |
| |
| export async function openReportViewerPage( |
| page: Page, |
| context: BrowserContext, |
| similarityScope?: ViewerScope, |
| similarityLocator?: Locator, |
| ): Promise<ViewerScope> { |
| const scope = similarityScope || (await resolveAssignmentScope(page)); |
|
|
| const titleButton = scope |
| .locator(SELECTORS.viewer.titleButton) |
| .first(); |
|
|
| const specificTargetVisible = similarityLocator |
| ? await similarityLocator.isVisible({ timeout: 3000 }).catch(() => false) |
| : false; |
| const clickTarget = specificTargetVisible |
| ? similarityLocator! |
| : (await titleButton.isVisible({ timeout: 3000 }).catch(() => false)) |
| ? titleButton |
| : null; |
|
|
| const [newPage] = await Promise.all([ |
| context |
| .waitForEvent('page', { timeout: 30000 }) |
| .catch(() => null), |
| clickTarget |
| ? clickTarget.click({ force: true }).catch(async () => { |
| await deepClickByText(scope, ['view submission']); |
| }) |
| : deepClickByText(scope, ['view submission']), |
| ]); |
|
|
| |
| |
| |
| let viewerScope: ViewerScope; |
| if (newPage && isReportSubmissionUrl(newPage.url())) { |
| |
| viewerScope = newPage; |
| await (viewerScope as Page) |
| .waitForLoadState('domcontentloaded', { timeout: 60000 }) |
| .catch(() => {}); |
| } else { |
| |
| if (newPage) { |
| logger.warn('waitForEvent(page) captured a non-viewer page; scanning context for viewer', { |
| capturedUrl: newPage.url(), |
| }); |
| } |
| |
| const scopeOwner = typeof (scope as any).page === 'function' |
| ? (scope as Frame).page() |
| : (scope as Page); |
| await scopeOwner.waitForTimeout(3000).catch(() => {}); |
| const existing = findReportViewerScope(context); |
| viewerScope = existing || |
| (typeof (scope as any).page === 'function' |
| ? (scope as Frame).page() |
| : (scope as Page)); |
| } |
|
|
| const deadline = Date.now() + 30000; |
| while ( |
| Date.now() < deadline && |
| !isReportSubmissionUrl(viewerScope.url()) |
| ) { |
| const existing = findReportViewerScope(context); |
| if (existing) { |
| viewerScope = existing; |
| break; |
| } |
| await ownerPage(viewerScope).waitForTimeout(500).catch(() => {}); |
| } |
|
|
| await ownerPage(viewerScope).waitForTimeout(2500); |
|
|
| const viewerUrl = viewerScope.url(); |
| logger.info('Report viewer opened', { viewerUrl }); |
|
|
| if (!isReportSubmissionUrl(viewerUrl)) { |
| throw new Error( |
| `Report viewer did not open a submission route correctly. Current URL: ${viewerUrl}`, |
| ); |
| } |
|
|
| return viewerScope; |
| } |
|
|
| export async function openReportViewer( |
| page: Page, |
| context: BrowserContext, |
| ): Promise<string> { |
| const viewerPage = await openReportViewerPage(page, context); |
| return viewerPage.url(); |
| } |
|
|