Navigate to URL for authentication (#2493)
#### Details Navigate to URL for authentication when supported authentication was detected. #### Pull request checklist <!-- If a checklist item is not applicable to this change, write "n/a" in the checkbox --> - [ ] Addresses an existing issue: Fixes #0000 - [x] Added relevant unit test for your changes. (`yarn test`) - [ ] Verified code coverage for the changes made. Check coverage report at: `<rootDir>/test-results/unit/coverage` - [ ] Ran precheckin (`yarn precheckin`) - [x] Validated in an Azure resource group
This commit is contained in:
Родитель
aa6707711b
Коммит
01ac5c1431
|
@ -10,6 +10,7 @@ node_modules*
|
|||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
|
||||
chrome-user-data/
|
||||
dist/
|
||||
dist*
|
||||
drop/
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
},
|
||||
"homepage": "https://github.com/Microsoft/accessibility-insights-service#readme",
|
||||
"devDependencies": {
|
||||
"@types/fingerprint-generator": "1.0.0",
|
||||
"@types/jest": "^29.5.0",
|
||||
"@types/lodash": "^4.14.182",
|
||||
"@types/node": "^16.18.11",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "accessibility-insights-scan",
|
||||
"version": "2.4.3",
|
||||
"version": "2.4.4",
|
||||
"description": "This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.microsoft.com.",
|
||||
"scripts": {
|
||||
"build": "webpack --config ./webpack.config.js \"$@\"",
|
||||
|
@ -29,7 +29,6 @@
|
|||
"standAlonePackage": "This is a stand-alone package. Do NOT add dependencies to any service packages.",
|
||||
"devDependencies": {
|
||||
"@types/escape-html": "^1.0.2",
|
||||
"@types/fingerprint-generator": "1.0.0",
|
||||
"@types/jest": "^29.5.0",
|
||||
"@types/lodash": "^4.14.182",
|
||||
"@types/node": "^16.18.11",
|
||||
|
|
|
@ -2,16 +2,25 @@
|
|||
// Licensed under the MIT License.
|
||||
|
||||
import 'reflect-metadata';
|
||||
|
||||
import { listMonorepoPackageNames } from 'common';
|
||||
import _ from 'lodash';
|
||||
import * as packageJson from '../package.json';
|
||||
|
||||
const acceptedMonorepoDependencies = ['accessibility-insights-crawler', 'axe-result-converter', 'common'];
|
||||
|
||||
describe('package.json dependencies', () => {
|
||||
const monorepoPackageNames = listMonorepoPackageNames();
|
||||
const isMonorepoPackage = (packageName: string) => monorepoPackageNames.includes(packageName);
|
||||
const monorepoDevDependencies = Object.keys(packageJson.devDependencies).filter(isMonorepoPackage);
|
||||
const monorepoNonDevDependencies = Object.keys(packageJson.dependencies).filter(isMonorepoPackage);
|
||||
|
||||
// We do NOT allow to have any dependencies on service monorepo packages
|
||||
// since cli package is a purely stand-alone package.
|
||||
it('does not include any dependencies on service monorepo packages', () => {
|
||||
expect(monorepoDevDependencies).toEqual(acceptedMonorepoDependencies);
|
||||
});
|
||||
|
||||
// We don't publish other monorepo packages (eg, "common") to npm, so it's important
|
||||
// that we only depend on them as devDependencies, not dependencies, to avoid consumers
|
||||
// trying to pull down non-published packages.
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Open browser in automation mode
|
||||
|
||||
set -eo pipefail
|
||||
|
||||
exitWithUsageInfo() {
|
||||
echo "
|
||||
Usage: ${BASH_SOURCE} -b <browser application path> [-p <profile location path>]
|
||||
"
|
||||
exit 1
|
||||
}
|
||||
|
||||
while getopts ":b:p:" option; do
|
||||
case $option in
|
||||
b) browserPath=${OPTARG} ;;
|
||||
p) profilePath=${OPTARG} ;;
|
||||
*) exitWithUsageInfo ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z ${browserPath} ]]; then
|
||||
exitWithUsageInfo
|
||||
fi
|
||||
|
||||
if [[ -z ${profilePath} ]]; then
|
||||
profilePath="${0%/*}/chrome-user-data"
|
||||
fi
|
||||
|
||||
result=$(
|
||||
"${browserPath}" \
|
||||
--allow-pre-commit-input \
|
||||
--disable-background-networking \
|
||||
--disable-background-timer-throttling \
|
||||
--disable-backgrounding-occluded-windows \
|
||||
--disable-breakpad \
|
||||
--disable-client-side-phishing-detection \
|
||||
--disable-component-update \
|
||||
--disable-features=Translate,AcceptCHFrame,MediaRouter,OptimizationHints,Prerender2 \
|
||||
--disable-hang-monitor \
|
||||
--disable-ipc-flooding-protection \
|
||||
--disable-popup-blocking \
|
||||
--disable-prompt-on-repost \
|
||||
--disable-renderer-backgrounding \
|
||||
--disable-search-engine-choice-screen \
|
||||
--disable-sync \
|
||||
--enable-automation \
|
||||
--enable-blink-features=IdleDetection \
|
||||
--enable-features=NetworkServiceInProcess2 \
|
||||
--export-tagged-pdf \
|
||||
--force-color-profile=srgb \
|
||||
--metrics-recording-only \
|
||||
--no-first-run \
|
||||
--password-store=basic \
|
||||
--use-mock-keychain \
|
||||
--enable-remote-extensions \
|
||||
--disable-blink-features=AutomationControlled \
|
||||
--remote-debugging-port=0 \
|
||||
--flag-switches-begin \
|
||||
--flag-switches-end \
|
||||
--disable-nacl \
|
||||
--user-data-dir="${profilePath}" \
|
||||
--disable-dev-shm-usage \
|
||||
--no-sandbox \
|
||||
--disable-setuid-sandbox \
|
||||
--disable-gpu \
|
||||
--disable-webgl \
|
||||
--disable-webgl2 \
|
||||
--disable-features=BackForwardCache \
|
||||
--js-flags=--max-old-space-size=8192 \
|
||||
--window-size=1920,1080 \
|
||||
about:blank
|
||||
)
|
|
@ -3,62 +3,90 @@
|
|||
|
||||
import 'reflect-metadata';
|
||||
|
||||
import { IMock, Mock } from 'typemoq';
|
||||
import { IMock, Mock, Times } from 'typemoq';
|
||||
import * as Puppeteer from 'puppeteer';
|
||||
import { GlobalLogger } from 'logger';
|
||||
import { System } from 'common';
|
||||
import { NavigationResponse } from '../page-navigator';
|
||||
import { PageResponseProcessor } from '../page-response-processor';
|
||||
import { puppeteerTimeoutConfig } from '../page-timeout-config';
|
||||
import { BrowserError } from '../browser-error';
|
||||
import { ResourceAuthenticator } from './resource-authenticator';
|
||||
import { LoginPageDetector } from './login-page-detector';
|
||||
import { LoginPageClientFactory } from './login-page-client-factory';
|
||||
import { LoginPageClient } from './azure-login-page-client';
|
||||
|
||||
const url = 'authUrl';
|
||||
|
||||
let loginPageDetectorMock: IMock<LoginPageDetector>;
|
||||
let loginPageClientFactoryMock: IMock<LoginPageClientFactory>;
|
||||
let puppeteerPageMock: IMock<Puppeteer.Page>;
|
||||
let loginPageClientMock: IMock<LoginPageClient>;
|
||||
let pageResponseProcessorMock: IMock<PageResponseProcessor>;
|
||||
let loggerMock: IMock<GlobalLogger>;
|
||||
let resourceAuthenticator: ResourceAuthenticator;
|
||||
let puppeteerGotoResponse: Puppeteer.HTTPResponse;
|
||||
|
||||
describe(ResourceAuthenticator, () => {
|
||||
beforeEach(() => {
|
||||
loginPageClientMock = Mock.ofType<LoginPageClient>();
|
||||
loginPageDetectorMock = Mock.ofType<LoginPageDetector>();
|
||||
loginPageClientFactoryMock = Mock.ofType<LoginPageClientFactory>();
|
||||
puppeteerPageMock = Mock.ofType<Puppeteer.Page>();
|
||||
pageResponseProcessorMock = Mock.ofType(PageResponseProcessor);
|
||||
loggerMock = Mock.ofType<GlobalLogger>();
|
||||
|
||||
puppeteerPageMock
|
||||
.setup((o) => o.url())
|
||||
.returns(() => url)
|
||||
.verifiable();
|
||||
System.getElapsedTime = () => 100;
|
||||
|
||||
resourceAuthenticator = new ResourceAuthenticator(
|
||||
loginPageDetectorMock.object,
|
||||
loginPageClientFactoryMock.object,
|
||||
pageResponseProcessorMock.object,
|
||||
loggerMock.object,
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
loginPageDetectorMock.verifyAll();
|
||||
loginPageClientFactoryMock.verifyAll();
|
||||
puppeteerPageMock.verifyAll();
|
||||
loginPageClientMock.verifyAll();
|
||||
pageResponseProcessorMock.verifyAll();
|
||||
loggerMock.verifyAll();
|
||||
});
|
||||
|
||||
it('should return navigation error', async () => {
|
||||
const browserError = { statusCode: 404 } as BrowserError;
|
||||
const gotoError = new Error('404');
|
||||
puppeteerPageMock
|
||||
.setup((o) => o.goto(url, { waitUntil: 'networkidle2', timeout: puppeteerTimeoutConfig.navigationTimeoutMsec }))
|
||||
.returns(() => Promise.reject(gotoError))
|
||||
.verifiable(Times.atLeastOnce());
|
||||
pageResponseProcessorMock
|
||||
.setup((o) => o.getNavigationError(gotoError))
|
||||
.returns(() => browserError)
|
||||
.verifiable();
|
||||
const authenticationResult = {
|
||||
navigationResponse: {
|
||||
browserError,
|
||||
pageNavigationTiming: {
|
||||
goto: 100,
|
||||
},
|
||||
} as NavigationResponse,
|
||||
authenticationType: 'entraId',
|
||||
authenticated: false,
|
||||
};
|
||||
|
||||
const response = await resourceAuthenticator.authenticate(url, 'entraId', puppeteerPageMock.object);
|
||||
expect(response).toEqual(authenticationResult);
|
||||
});
|
||||
|
||||
it('should authenticate resource', async () => {
|
||||
const authenticationResult = {
|
||||
navigationResponse: { httpResponse: { url: () => 'url' } } as NavigationResponse,
|
||||
authenticationType: 'entraId',
|
||||
authenticated: true,
|
||||
};
|
||||
loginPageDetectorMock
|
||||
.setup((o) => o.getAuthenticationType(url))
|
||||
.returns(() => 'entraId')
|
||||
.verifiable();
|
||||
puppeteerGotoResponse = { puppeteerResponse: 'goto', url: () => url } as unknown as Puppeteer.HTTPResponse;
|
||||
puppeteerPageMock
|
||||
.setup((o) => o.goto(url, { waitUntil: 'networkidle2', timeout: puppeteerTimeoutConfig.navigationTimeoutMsec }))
|
||||
.returns(() => Promise.resolve(puppeteerGotoResponse))
|
||||
.verifiable(Times.atLeastOnce());
|
||||
loginPageClientMock
|
||||
.setup((o) => o.login(puppeteerPageMock.object))
|
||||
.returns(() => Promise.resolve(authenticationResult.navigationResponse))
|
||||
|
@ -68,27 +96,7 @@ describe(ResourceAuthenticator, () => {
|
|||
.returns(() => loginPageClientMock.object)
|
||||
.verifiable();
|
||||
|
||||
const response = await resourceAuthenticator.authenticate(puppeteerPageMock.object);
|
||||
const response = await resourceAuthenticator.authenticate(url, 'entraId', puppeteerPageMock.object);
|
||||
expect(response).toEqual(authenticationResult);
|
||||
});
|
||||
|
||||
it('should skip if no login page detected', async () => {
|
||||
loginPageDetectorMock
|
||||
.setup((o) => o.getAuthenticationType(url))
|
||||
.returns(() => undefined)
|
||||
.verifiable();
|
||||
|
||||
const response = await resourceAuthenticator.authenticate(puppeteerPageMock.object);
|
||||
expect(response).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should skip if undetermined authentication detected', async () => {
|
||||
loginPageDetectorMock
|
||||
.setup((o) => o.getAuthenticationType(url))
|
||||
.returns(() => 'undetermined')
|
||||
.verifiable();
|
||||
|
||||
const response = await resourceAuthenticator.authenticate(puppeteerPageMock.object);
|
||||
expect(response).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,8 +6,9 @@ import * as Puppeteer from 'puppeteer';
|
|||
import { GlobalLogger } from 'logger';
|
||||
import { AuthenticationType } from 'storage-documents';
|
||||
import { System } from 'common';
|
||||
import { NavigationResponse } from '../page-navigator';
|
||||
import { LoginPageDetector } from './login-page-detector';
|
||||
import { NavigationResponse, PageOperationResult } from '../page-navigator';
|
||||
import { PageNavigationTiming, puppeteerTimeoutConfig } from '../page-timeout-config';
|
||||
import { PageResponseProcessor } from '../page-response-processor';
|
||||
import { LoginPageClientFactory } from './login-page-client-factory';
|
||||
|
||||
export interface ResourceAuthenticationResult {
|
||||
|
@ -19,15 +20,27 @@ export interface ResourceAuthenticationResult {
|
|||
@injectable()
|
||||
export class ResourceAuthenticator {
|
||||
constructor(
|
||||
@inject(LoginPageDetector) private readonly loginPageDetector: LoginPageDetector,
|
||||
@inject(LoginPageClientFactory) private readonly loginPageClientFactory: LoginPageClientFactory,
|
||||
@inject(PageResponseProcessor) public readonly pageResponseProcessor: PageResponseProcessor,
|
||||
@inject(GlobalLogger) @optional() private readonly logger: GlobalLogger,
|
||||
) {}
|
||||
|
||||
public async authenticate(page: Puppeteer.Page): Promise<ResourceAuthenticationResult> {
|
||||
const authenticationType = this.loginPageDetector.getAuthenticationType(page.url());
|
||||
if (authenticationType === undefined || authenticationType === 'undetermined') {
|
||||
return undefined;
|
||||
public async authenticate(
|
||||
url: string,
|
||||
authenticationType: AuthenticationType,
|
||||
page: Puppeteer.Page,
|
||||
): Promise<ResourceAuthenticationResult> {
|
||||
const operationResult = await this.navigatePage(url, page);
|
||||
if (operationResult.browserError) {
|
||||
return {
|
||||
navigationResponse: {
|
||||
httpResponse: operationResult.response,
|
||||
pageNavigationTiming: operationResult.navigationTiming,
|
||||
browserError: operationResult.browserError,
|
||||
},
|
||||
authenticationType,
|
||||
authenticated: false,
|
||||
};
|
||||
}
|
||||
|
||||
const loginPageClient = this.loginPageClientFactory.getPageClient(authenticationType);
|
||||
|
@ -53,4 +66,27 @@ export class ResourceAuthenticator {
|
|||
authenticated,
|
||||
};
|
||||
}
|
||||
|
||||
private async navigatePage(url: string, page: Puppeteer.Page): Promise<PageOperationResult> {
|
||||
const timestamp = System.getTimestamp();
|
||||
try {
|
||||
this.logger?.logInfo('Navigate page to URL for authentication.');
|
||||
const response = await page.goto(url, { waitUntil: 'networkidle2', timeout: puppeteerTimeoutConfig.navigationTimeoutMsec });
|
||||
|
||||
return { response, navigationTiming: { goto: System.getElapsedTime(timestamp) } as PageNavigationTiming };
|
||||
} catch (error) {
|
||||
const browserError = this.pageResponseProcessor.getNavigationError(error as Error);
|
||||
this.logger?.logError(`Page authenticator navigation error.`, {
|
||||
error: System.serializeError(error),
|
||||
browserError: System.serializeError(browserError),
|
||||
});
|
||||
|
||||
return {
|
||||
response: undefined,
|
||||
navigationTiming: { goto: System.getElapsedTime(timestamp) } as PageNavigationTiming,
|
||||
browserError,
|
||||
error,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,57 +65,61 @@ export class PageNavigator {
|
|||
|
||||
public async waitForNavigation(page: Puppeteer.Page): Promise<NavigationResponse> {
|
||||
const pageOperation = this.createPageOperation('wait', page);
|
||||
const opResult = await this.pageOperationHandler.invoke(pageOperation, page);
|
||||
if (opResult.error) {
|
||||
return this.getOperationErrorResult(opResult);
|
||||
const operationResult = await this.pageOperationHandler.invoke(pageOperation, page);
|
||||
if (operationResult.error) {
|
||||
return this.getOperationErrorResult(operationResult);
|
||||
}
|
||||
|
||||
return {
|
||||
httpResponse: opResult.response,
|
||||
pageNavigationTiming: opResult.navigationTiming,
|
||||
browserError: opResult.browserError,
|
||||
httpResponse: operationResult.response,
|
||||
pageNavigationTiming: operationResult.navigationTiming,
|
||||
browserError: operationResult.browserError,
|
||||
};
|
||||
}
|
||||
|
||||
private async navigatePage(pageOperation: PageOperation, page: Puppeteer.Page): Promise<NavigationResponse> {
|
||||
const opResult = await this.navigatePageImpl(pageOperation, page);
|
||||
const operationResult = await this.navigatePageImpl(pageOperation, page);
|
||||
|
||||
if (opResult.browserError) {
|
||||
if (operationResult.browserError) {
|
||||
return {
|
||||
httpResponse: undefined,
|
||||
pageNavigationTiming: opResult.navigationTiming,
|
||||
browserError: opResult.browserError,
|
||||
pageNavigationTiming: operationResult.navigationTiming,
|
||||
browserError: operationResult.browserError,
|
||||
};
|
||||
}
|
||||
|
||||
const postNavigationPageTiming = await this.pageNavigationHooks.postNavigation(page, opResult.response, async (browserError) => {
|
||||
opResult.browserError = browserError;
|
||||
});
|
||||
const postNavigationPageTiming = await this.pageNavigationHooks.postNavigation(
|
||||
page,
|
||||
operationResult.response,
|
||||
async (browserError) => {
|
||||
operationResult.browserError = browserError;
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
httpResponse: opResult.response,
|
||||
httpResponse: operationResult.response,
|
||||
pageNavigationTiming: {
|
||||
...opResult.navigationTiming,
|
||||
...operationResult.navigationTiming,
|
||||
...postNavigationPageTiming,
|
||||
} as PageNavigationTiming,
|
||||
browserError: opResult.browserError,
|
||||
browserError: operationResult.browserError,
|
||||
};
|
||||
}
|
||||
|
||||
private async navigatePageImpl(pageOperation: PageOperation, page: Puppeteer.Page): Promise<PageOperationResult> {
|
||||
let opResult = await this.pageOperationHandler.invoke(pageOperation, page);
|
||||
if (opResult.error) {
|
||||
return this.getOperationErrorResult(opResult);
|
||||
let operationResult = await this.pageOperationHandler.invoke(pageOperation, page);
|
||||
if (operationResult.error) {
|
||||
return this.getOperationErrorResult(operationResult);
|
||||
}
|
||||
|
||||
opResult = await this.handleCachedResponse(opResult, page);
|
||||
if (opResult.error) {
|
||||
return this.getOperationErrorResult(opResult);
|
||||
operationResult = await this.handleCachedResponse(operationResult, page);
|
||||
if (operationResult.error) {
|
||||
return this.getOperationErrorResult(operationResult);
|
||||
}
|
||||
|
||||
await this.resetPageSessionHistory(page);
|
||||
|
||||
return opResult;
|
||||
return operationResult;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -134,7 +138,7 @@ export class PageNavigator {
|
|||
});
|
||||
|
||||
let count = 0;
|
||||
let opResult;
|
||||
let operationResult;
|
||||
do {
|
||||
count++;
|
||||
if (count > maxRetryCount - 1) {
|
||||
|
@ -155,13 +159,15 @@ export class PageNavigator {
|
|||
// Navigation using page.goto() will not resolve HTTP 304 response
|
||||
// Use of page.goBack() is required with back/forward cache disabled, option --disable-features=BackForwardCache
|
||||
const pageOperation = async () => page.goBack(this.waitForOptions);
|
||||
opResult = await this.pageOperationHandler.invoke(pageOperation, page);
|
||||
operationResult = await this.pageOperationHandler.invoke(pageOperation, page);
|
||||
} while (
|
||||
count < maxRetryCount &&
|
||||
(opResult.error !== undefined || opResult.response?.status() === undefined || opResult.response?.status() === 304)
|
||||
(operationResult.error !== undefined ||
|
||||
operationResult.response?.status() === undefined ||
|
||||
operationResult.response?.status() === 304)
|
||||
);
|
||||
|
||||
return opResult;
|
||||
return operationResult;
|
||||
}
|
||||
|
||||
private createPageOperation(operation: 'goto' | 'reload' | 'wait', page: Puppeteer.Page, url?: string): PageOperation {
|
||||
|
|
|
@ -10,7 +10,7 @@ import { GuidGenerator, System } from 'common';
|
|||
import { GlobalLogger } from 'logger';
|
||||
import { AxeScanResults } from './axe-scanner/axe-scan-results';
|
||||
import { BrowserError } from './browser-error';
|
||||
import { Page } from './page';
|
||||
import { BrowserStartOptions, Page } from './page';
|
||||
import { getPromisableDynamicMock } from './test-utilities/promisable-mock';
|
||||
import { WebDriver } from './web-driver';
|
||||
import { PageNavigator, NavigationResponse } from './page-navigator';
|
||||
|
@ -54,6 +54,7 @@ let resourceAuthenticatorMock: IMock<ResourceAuthenticator>;
|
|||
let pageAnalyzerMock: IMock<PageAnalyzer>;
|
||||
let guidGeneratorMock: IMock<GuidGenerator>;
|
||||
let devToolsSessionMock: IMock<DevToolsSession>;
|
||||
let browserStartOptions: BrowserStartOptions;
|
||||
|
||||
describe(Page, () => {
|
||||
beforeEach(() => {
|
||||
|
@ -78,6 +79,7 @@ describe(Page, () => {
|
|||
pageAnalyzerMock = Mock.ofType<PageAnalyzer>();
|
||||
guidGeneratorMock = Mock.ofType<GuidGenerator>();
|
||||
devToolsSessionMock = Mock.ofType<DevToolsSession>();
|
||||
browserStartOptions = {} as BrowserStartOptions;
|
||||
|
||||
scrollToTopMock = jest.fn().mockImplementation(() => Promise.resolve());
|
||||
puppeteerResponseMock.setup((o) => o.ok()).returns(() => true);
|
||||
|
@ -141,6 +143,7 @@ describe(Page, () => {
|
|||
timing[key] = `${navigationResponse.pageNavigationTiming[key]}`;
|
||||
});
|
||||
loggerMock.setup((o) => o.logInfo('Total page load time 8, msec', { status: 200, ...timing })).verifiable();
|
||||
page.browserStartOptions = browserStartOptions;
|
||||
|
||||
await page.navigate(url);
|
||||
|
||||
|
@ -161,16 +164,19 @@ describe(Page, () => {
|
|||
pageAnalyzerMock.reset();
|
||||
pageAnalyzerMock
|
||||
.setup((o) => o.analyze(url, puppeteerPageMock.object))
|
||||
.returns(() => Promise.resolve({ navigationResponse, authentication: true } as PageAnalysisResult))
|
||||
.returns(() =>
|
||||
Promise.resolve({ navigationResponse, authentication: true, authenticationType: 'entraId' } as PageAnalysisResult),
|
||||
)
|
||||
.verifiable();
|
||||
resourceAuthenticatorMock
|
||||
.setup((o) => o.authenticate(puppeteerPageMock.object))
|
||||
.setup((o) => o.authenticate(url, 'entraId', puppeteerPageMock.object))
|
||||
.returns(() => Promise.resolve(authenticationResult))
|
||||
.verifiable();
|
||||
pageNavigatorMock
|
||||
.setup(async (o) => o.navigate('localhost/2', puppeteerPageMock.object))
|
||||
.returns(() => Promise.resolve(reloadNavigationResponse))
|
||||
.verifiable();
|
||||
page.browserStartOptions = browserStartOptions;
|
||||
|
||||
await page.navigate(url, { enableAuthentication: true });
|
||||
|
||||
|
@ -186,6 +192,7 @@ describe(Page, () => {
|
|||
.setup(async (o) => o.trace(url, puppeteerPageMock.object))
|
||||
.returns(() => Promise.resolve())
|
||||
.verifiable();
|
||||
page.browserStartOptions = browserStartOptions;
|
||||
|
||||
await page.navigate(url);
|
||||
|
||||
|
@ -203,6 +210,7 @@ describe(Page, () => {
|
|||
.setup((o) => o.setExtraHTTPHeaders({ X_FORWARDED_FOR: '1.1.1.1' }))
|
||||
.returns(() => Promise.resolve())
|
||||
.verifiable();
|
||||
page.browserStartOptions = browserStartOptions;
|
||||
|
||||
await page.navigate(url);
|
||||
});
|
||||
|
@ -350,6 +358,7 @@ describe(Page, () => {
|
|||
.setup(async (o) => o.close())
|
||||
.returns(() => Promise.resolve())
|
||||
.verifiable();
|
||||
page.browserStartOptions = browserStartOptions;
|
||||
|
||||
await page.close();
|
||||
});
|
||||
|
|
|
@ -64,6 +64,8 @@ export class Page {
|
|||
|
||||
private readonly enableAuthenticationGlobalFlag: boolean;
|
||||
|
||||
private readonly browserWSEndpoint: string;
|
||||
|
||||
constructor(
|
||||
@inject(WebDriver) private readonly webDriver: WebDriver,
|
||||
@inject(PageNavigator) private readonly pageNavigator: PageNavigator,
|
||||
|
@ -76,6 +78,7 @@ export class Page {
|
|||
private readonly scrollToPageTop: typeof scrollToTop = scrollToTop,
|
||||
) {
|
||||
this.enableAuthenticationGlobalFlag = process.env.PAGE_AUTH === 'true' ? true : false;
|
||||
this.browserWSEndpoint = process.env.BROWSER_ENDPOINT;
|
||||
}
|
||||
|
||||
public get puppeteerPage(): Puppeteer.Page {
|
||||
|
@ -88,8 +91,8 @@ export class Page {
|
|||
|
||||
public async create(options: BrowserStartOptions = { clearBrowserCache: true }): Promise<void> {
|
||||
this.browserStartOptions = options;
|
||||
if (options?.browserWSEndpoint !== undefined) {
|
||||
this.browser = await this.webDriver.connect(options?.browserWSEndpoint);
|
||||
if (!isEmpty(options?.browserWSEndpoint) || !isEmpty(this.browserWSEndpoint)) {
|
||||
this.browser = await this.webDriver.connect(options?.browserWSEndpoint ?? this.browserWSEndpoint);
|
||||
} else {
|
||||
this.browser = await this.webDriver.launch({
|
||||
browserExecutablePath: options?.browserExecutablePath,
|
||||
|
@ -213,6 +216,10 @@ export class Page {
|
|||
}
|
||||
|
||||
public async close(): Promise<void> {
|
||||
if (this.browserStartOptions.browserWSEndpoint || this.browserWSEndpoint) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.webDriver !== undefined) {
|
||||
await this.webDriver.close();
|
||||
}
|
||||
|
@ -267,7 +274,12 @@ export class Page {
|
|||
}
|
||||
|
||||
// Invoke authentication client
|
||||
this.authenticationResult = await this.resourceAuthenticator.authenticate(this.page);
|
||||
this.authenticationResult = await this.resourceAuthenticator.authenticate(
|
||||
this.requestUrl,
|
||||
this.pageAnalysisResult.authenticationType,
|
||||
this.page,
|
||||
);
|
||||
|
||||
if (this.authenticationResult?.navigationResponse?.browserError !== undefined) {
|
||||
this.setLastNavigationState('auth', this.authenticationResult.navigationResponse);
|
||||
}
|
||||
|
@ -297,6 +309,10 @@ export class Page {
|
|||
}
|
||||
|
||||
private async reopenBrowser(): Promise<void> {
|
||||
if (this.browserStartOptions.browserWSEndpoint || this.browserWSEndpoint) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.close();
|
||||
await this.create({ ...this.browserStartOptions, clearBrowserCache: false });
|
||||
// wait for browser to start
|
||||
|
|
|
@ -110,7 +110,7 @@ export class PageScanProcessor {
|
|||
if (authenticationResult !== undefined) {
|
||||
pageScanResult.authentication = {
|
||||
...pageScanResult.authentication,
|
||||
detected: authenticationResult.authenticationType,
|
||||
detected: pageMetadata.authenticationType,
|
||||
state: authenticationResult.authenticated === true ? 'succeeded' : 'failed',
|
||||
};
|
||||
} else {
|
||||
|
|
|
@ -4867,7 +4867,6 @@ __metadata:
|
|||
"@opentelemetry/semantic-conventions": ^1.15.0
|
||||
"@sindresorhus/fnv1a": ^2.0.1
|
||||
"@types/escape-html": ^1.0.2
|
||||
"@types/fingerprint-generator": 1.0.0
|
||||
"@types/jest": ^29.5.0
|
||||
"@types/lodash": ^4.14.182
|
||||
"@types/node": ^16.18.11
|
||||
|
@ -5491,6 +5490,7 @@ __metadata:
|
|||
version: 0.0.0-use.local
|
||||
resolution: "axe-result-converter@workspace:packages/axe-result-converter"
|
||||
dependencies:
|
||||
"@types/fingerprint-generator": 1.0.0
|
||||
"@types/jest": ^29.5.0
|
||||
"@types/lodash": ^4.14.182
|
||||
"@types/node": ^16.18.11
|
||||
|
|
Загрузка…
Ссылка в новой задаче