Spaces:
Sleeping
Sleeping
| 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; | |
| } | |
| /** | |
| * Click the submission title to open the report viewer. | |
| * Returns the viewer page after it has navigated to reports.integrity.turnitin.com. | |
| */ | |
| 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']), | |
| ]); | |
| // BUG-6 FIX: Verify the captured page is actually the report viewer before | |
| // waiting for its load state. context.waitForEvent('page') can fire for any | |
| // new page (ads, redirects), so we must not blindly wait 30s on a wrong page. | |
| let viewerScope: ViewerScope; | |
| if (newPage && isReportSubmissionUrl(newPage.url())) { | |
| // Correct page — wait for it to load normally | |
| viewerScope = newPage; | |
| await (viewerScope as Page) | |
| .waitForLoadState('domcontentloaded', { timeout: 60000 }) | |
| .catch(() => {}); | |
| } else { | |
| // Wrong page or no page event — scan existing pages/frames immediately | |
| if (newPage) { | |
| logger.warn('waitForEvent(page) captured a non-viewer page; scanning context for viewer', { | |
| capturedUrl: newPage.url(), | |
| }); | |
| } | |
| // Give Turnitin a short moment to open the real viewer page | |
| 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(); | |
| } | |