Spaces:
Sleeping
Sleeping
File size: 4,216 Bytes
d5c9d40 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 | 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();
}
|