Applied no-unnecessary-condition rule

This commit is contained in:
Adam Gastineau 2024-12-17 09:56:44 -08:00
parent b77146632b
commit e029e78ebe
140 changed files with 464 additions and 268 deletions

View file

@ -14,6 +14,9 @@ module.exports = {
settings: {
react: { version: "18" }
},
ignorePatterns: [
'/packages/playwright/bundles/expect/third_party/**/*'
],
overrides: [
{
files: ['./examples/**/*'],
@ -34,7 +37,7 @@ module.exports = {
rules: {
"@typescript-eslint/no-unused-vars": [2, {args: "none"}],
"@typescript-eslint/consistent-type-imports": [2, {disallowTypeAnnotations: false}],
// "@typescript-eslint/no-unnecessary-condition": [2],
"@typescript-eslint/no-unnecessary-condition": [2],
/**
* Enforced rules

View file

@ -78,7 +78,7 @@ export const ReportView: React.FC<{
return <div className='htmlreport vbox px-4 pb-4'>
<main>
{report?.json() && <HeaderView stats={report.json().stats} filterText={filterText} setFilterText={setFilterText}></HeaderView>}
{report?.json().metadata && <MetadataView {...report?.json().metadata as Metainfo} />}
{report?.json().metadata && <MetadataView {...report.json().metadata as Metainfo} />}
<Route predicate={testFilesRoutePredicate}>
<TestFilesHeader report={report?.json()} filteredStats={filteredStats} />
<TestFilesView

View file

@ -47,7 +47,7 @@ export const TestCaseView: React.FC<{
}, [test]);
const visibleAnnotations = React.useMemo(() => {
return test?.annotations?.filter(annotation => !annotation.type.startsWith('_')) || [];
return test?.annotations.filter(annotation => !annotation.type.startsWith('_')) || [];
}, [test?.annotations]);
return <div className='test-case-column vbox'>
@ -58,10 +58,10 @@ export const TestCaseView: React.FC<{
<div style={{ width: 10 }}></div>
<div className={clsx(!next && 'hidden')}><Link href={testResultHref({ test: next }) + filterParam}>next »</Link></div>
</div>}
{test && <div className='test-case-title'>{test?.title}</div>}
{test && <div className='test-case-title'>{test.title}</div>}
{test && <div className='hbox'>
<div className='test-case-location'>
<CopyToClipboardContainer value={`${test?.location.file}:${test?.location.line}`}>
<CopyToClipboardContainer value={`${test.location.file}:${test.location.line}`}>
{test.location.file}:{test.location.line}
</CopyToClipboardContainer>
</div>
@ -69,7 +69,7 @@ export const TestCaseView: React.FC<{
<div className='test-case-duration'>{msToString(test.duration)}</div>
</div>}
{test && (!!test.projectName || labels) && <div className='test-case-project-labels-row'>
{test && !!test.projectName && <ProjectLink projectNames={projectNames} projectName={test.projectName}></ProjectLink>}
{!!test.projectName && <ProjectLink projectNames={projectNames} projectName={test.projectName}></ProjectLink>}
{labels && <LabelsLinkView labels={labels} />}
</div>}
{!!visibleAnnotations.length && <AutoChip header='Annotations'>
@ -80,7 +80,7 @@ export const TestCaseView: React.FC<{
id: String(index),
title: <div style={{ display: 'flex', alignItems: 'center' }}>{statusIcon(result.status)} {retryLabel(index)}</div>,
render: () => <TestResultView test={test!} result={result} />
})) || []} selectedTab={String(selectedResultIndex)} setSelectedTab={id => setSelectedResultIndex(+id)} />}
}))} selectedTab={String(selectedResultIndex)} setSelectedTab={id => setSelectedResultIndex(+id)} />}
</div>;
};

View file

@ -86,9 +86,9 @@ function videoBadge(test: TestCaseSummary): JSX.Element | undefined {
return resultWithVideo ? <Link href={testResultHref({ test, result: resultWithVideo, anchor: 'attachment-video' })} title='View video' className='test-file-badge'>{video()}</Link> : undefined;
}
function traceBadge(test: TestCaseSummary): JSX.Element | undefined {
function traceBadge(test: TestCaseSummary): JSX.Element {
const firstTraces = test.results.map(result => result.attachments.filter(attachment => attachment.name === 'trace')).filter(traces => traces.length > 0)[0];
return firstTraces ? <Link href={generateTraceUrl(firstTraces)} title='View trace' className='test-file-badge'>{trace()}</Link> : undefined;
return <Link href={generateTraceUrl(firstTraces)} title='View trace' className='test-file-badge'>{trace()}</Link>;
}
const LabelsClickView: React.FC<React.PropsWithChildren<{

View file

@ -72,8 +72,8 @@ export const TestFilesHeader: React.FC<{
{report.projectNames.length === 1 && !!report.projectNames[0] && <div data-testid='project-name' style={{ color: 'var(--color-fg-subtle)' }}>Project: {report.projectNames[0]}</div>}
{filteredStats && <div data-testid='filtered-tests-count' style={{ color: 'var(--color-fg-subtle)', padding: '0 10px' }}>Filtered: {filteredStats.total} {!!filteredStats.total && ('(' + msToString(filteredStats.duration) + ')')}</div>}
<div style={{ flex: 'auto' }}></div>
<div data-testid='overall-time' style={{ color: 'var(--color-fg-subtle)', marginRight: '10px' }}>{report ? new Date(report.startTime).toLocaleString() : ''}</div>
<div data-testid='overall-duration' style={{ color: 'var(--color-fg-subtle)' }}>Total time: {msToString(report.duration ?? 0)}</div>
<div data-testid='overall-time' style={{ color: 'var(--color-fg-subtle)', marginRight: '10px' }}>{new Date(report.startTime).toLocaleString()}</div>
<div data-testid='overall-duration' style={{ color: 'var(--color-fg-subtle)' }}>Total time: {msToString(report.duration)}</div>
</div>
{!!report.errors.length && <AutoChip header='Errors' dataTestId='report-errors'>
{report.errors.map((error, index) => <TestErrorView key={'test-report-error-message-' + index} error={error}></TestErrorView>)}

View file

@ -77,7 +77,7 @@ export const TestResultView: React.FC<{
result: TestResult,
}> = ({ test, result }) => {
const { screenshots, videos, traces, otherAttachments, diffs, errors, otherAttachmentAnchors, screenshotAnchors } = React.useMemo(() => {
const attachments = result?.attachments || [];
const attachments = result.attachments;
const screenshots = new Set(attachments.filter(a => a.contentType.startsWith('image/')));
const screenshotAnchors = [...screenshots].map(a => `attachment-${a.name}`);
const videos = attachments.filter(a => a.contentType.startsWith('video/'));

View file

@ -38,6 +38,7 @@ export class BrowserServerLauncherImpl implements BrowserServerLauncher {
async launchServer(options: LaunchServerOptions = {}): Promise<BrowserServer> {
const playwright = createPlaywright({ sdkLanguage: 'javascript', isServer: true });
// TODO: enable socks proxy once ipv6 is supported.
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
const socksProxy = false ? new SocksProxy() : undefined;
playwright.options.socksProxyPort = await socksProxy?.listen(0);

View file

@ -116,9 +116,7 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
const pageObject = Page.from(page);
const parsedError = parseError(error);
this.emit(Events.BrowserContext.WebError, new WebError(pageObject, parsedError));
if (pageObject) {
pageObject.emit(Events.Page.PageError, parsedError);
}
pageObject.emit(Events.Page.PageError, parsedError);
});
this._channel.on('dialog', ({ dialog }) => {
const dialogObject = Dialog.from(dialog);

View file

@ -150,7 +150,7 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel> imple
let closeError: string | undefined;
const onPipeClosed = (reason?: string) => {
// Emulate all pages, contexts and the browser closing upon disconnect.
for (const context of browser.contexts() || []) {
for (const context of browser.contexts()) {
for (const page of context.pages()) {
page._onClose();
}

View file

@ -137,7 +137,7 @@ export class Connection extends EventEmitter {
}
const location = frames[0] ? { file: frames[0].file, line: frames[0].line, column: frames[0].column } : undefined;
const metadata: channels.Metadata = { apiName, location, internal: !apiName, stepId };
if (this._tracingCount && frames && type !== 'LocalUtils') {
if (this._tracingCount && type !== 'LocalUtils') {
this._localUtils?._channel.addStackToTracingNoReply({ callData: { stack: frames, id } }).catch(() => {});
}
// We need to exit zones before calling into the server, otherwise
@ -217,7 +217,7 @@ export class Connection extends EventEmitter {
private _tChannelImplFromWire(names: '*' | string[], arg: any, path: string, context: ValidatorContext) {
if (arg && typeof arg === 'object' && typeof arg.guid === 'string') {
const object = this._objects.get(arg.guid)!;
const object = this._objects.get(arg.guid);
if (!object) {
throw new Error(`Object with guid ${arg.guid} was not bound in the connection`);
}

View file

@ -28,7 +28,7 @@ export class ConsoleMessage implements api.ConsoleMessage {
private _event: channels.BrowserContextConsoleEvent | channels.ElectronApplicationConsoleEvent;
constructor(event: channels.BrowserContextConsoleEvent | channels.ElectronApplicationConsoleEvent) {
this._page = ('page' in event && event.page) ? Page.from(event.page) : null;
this._page = 'page' in event ? Page.from(event.page) : null;
this._event = event;
}

View file

@ -248,6 +248,7 @@ export function convertSelectOptionValues(values: string | api.ElementHandle | S
return {};
}
for (let i = 0; i < values.length; i++) {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
assert(values[i] !== null, `options[${i}]: expected object, got null`);
}
if (values[0] instanceof ElementHandle) {
@ -262,6 +263,7 @@ export function convertSelectOptionValues(values: string | api.ElementHandle | S
type SetInputFilesFiles = Pick<channels.ElementHandleSetInputFilesParams, 'payloads' | 'localPaths' | 'localDirectory' | 'streams' | 'directoryStream'>;
function filePayloadExceedsSizeLimit(payloads: FilePayload[]) {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
return payloads.reduce((size, item) => size + (item.buffer ? item.buffer.byteLength : 0), 0) >= fileUploadSizeLimit;
}

View file

@ -66,6 +66,7 @@ export class EventEmitter implements EventEmitterType {
}
const handler = events[type];
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (handler === undefined) {
return false;
}
@ -120,6 +121,7 @@ export class EventEmitter implements EventEmitterType {
} else {
// To avoid recursion in the case that type === "newListener"! Before
// adding it to the listeners, first emit "newListener".
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (events.newListener !== undefined) {
this.emit('newListener', type, unwrapListener(listener));
@ -194,6 +196,7 @@ export class EventEmitter implements EventEmitterType {
}
const list = events[type];
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (list === undefined) {
return this;
}
@ -203,6 +206,7 @@ export class EventEmitter implements EventEmitterType {
this._events = Object.create(null);
} else {
delete events[type];
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (events.removeListener) {
this.emit('removeListener', type, (list as any).listener ?? listener);
}
@ -233,6 +237,7 @@ export class EventEmitter implements EventEmitterType {
events[type] = list[0];
}
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (events.removeListener !== undefined) {
this.emit('removeListener', type, originalListener || listener);
}
@ -280,10 +285,12 @@ export class EventEmitter implements EventEmitterType {
}
// not listening for removeListener, no need to emit
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (!events.removeListener) {
if (type === undefined) {
this._events = Object.create(null);
this._eventsCount = 0;
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
} else if (events[type] !== undefined) {
if (--this._eventsCount === 0) {
this._events = Object.create(null);
@ -315,6 +322,7 @@ export class EventEmitter implements EventEmitterType {
if (typeof listeners === 'function') {
this.removeListener(type, listeners);
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
} else if (listeners !== undefined) {
// LIFO order
for (let i = listeners.length - 1; i >= 0; i--) {
@ -338,6 +346,7 @@ export class EventEmitter implements EventEmitterType {
if (typeof listener === 'function') {
return 1;
}
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (listener !== undefined) {
return listener.length;
}
@ -370,6 +379,7 @@ export class EventEmitter implements EventEmitterType {
}
const listener = events[type];
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (listener === undefined) {
return [];
}
@ -421,6 +431,6 @@ function unwrapListeners(arr: Listener[]): Listener[] {
return arr.map(l => wrappedListener(l) ?? l);
}
function wrappedListener(l: Listener): Listener {
function wrappedListener(l: Listener): Listener | undefined {
return (l as any).listener;
}

View file

@ -206,6 +206,7 @@ export class APIRequestContext extends ChannelOwner<channels.APIRequestContextCh
throw new Error(`Unexpected 'data' type`);
}
} else if (options.form) {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (globalThis.FormData && options.form instanceof FormData) {
formData = [];
for (const [name, value] of options.form.entries()) {
@ -219,6 +220,7 @@ export class APIRequestContext extends ChannelOwner<channels.APIRequestContextCh
}
} else if (options.multipart) {
multipartData = [];
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (globalThis.FormData && options.multipart instanceof FormData) {
const form = options.multipart;
for (const [name, value] of form.entries()) {

View file

@ -444,6 +444,7 @@ export class Frame extends ChannelOwner<channels.FrameChannel> implements api.Fr
async waitForFunction<R, Arg>(pageFunction: structs.PageFunction<Arg, R>, arg?: Arg, options: WaitForFunctionOptions = {}): Promise<structs.SmartHandle<R>> {
if (typeof options.polling === 'string') {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
assert(options.polling === 'raf', 'Unknown polling option: ' + options.polling);
}
const result = await this._channel.waitForFunction({

View file

@ -945,6 +945,7 @@ export class RawHeaders {
static _fromHeadersObjectLossy(headers: Headers): RawHeaders {
const headersArray: HeadersArray = Object.entries(headers).map(([name, value]) => ({
name, value
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
})).filter(header => header.value !== undefined);
return new RawHeaders(headersArray);
}
@ -958,7 +959,7 @@ export class RawHeaders {
get(name: string): string | null {
const values = this.getAll(name);
if (!values || !values.length) {
if (!values.length) {
return null;
}
return values.join(name.toLowerCase() === 'set-cookie' ? '\n' : ', ');

View file

@ -51,7 +51,7 @@ export class Playwright extends ChannelOwner<channels.PlaywrightChannel> {
this._bidiChromium._playwright = this;
this._bidiFirefox = BrowserType.from(initializer.bidiFirefox);
this._bidiFirefox._playwright = this;
this.devices = this._connection.localUtils().devices ?? {};
this.devices = this._connection.localUtils().devices;
this.selectors = new Selectors();
this.errors = { TimeoutError };

View file

@ -200,7 +200,7 @@ class SocksConnection {
private async _readBytes(length: number): Promise<Buffer> {
this._fence = this._offset + length;
if (!this._buffer || this._buffer.length < this._fence) {
if (this._buffer.length < this._fence) {
await new Promise<void>(f => this._fenceCallback = f);
}
this._offset += length;

View file

@ -20,4 +20,10 @@ export type Rect = Size & Point;
export type Quad = [ Point, Point, Point, Point ];
export type TimeoutOptions = { timeout?: number };
export type NameValue = { name: string, value: string };
export type HeadersArray = NameValue[];
export type HeadersArray = NameValue[];
export const assertUnreachable = (x: never): never => assertUnreachableWithError(x, new Error('Unreachable variant'));
export const assertUnreachableWithError = (x: never, error: Error): never => {
throw error;
};

View file

@ -15,6 +15,7 @@
*/
import type { SerializedValue } from '@protocol/channels';
import { assertUnreachable } from '../common/types';
export function parseSerializedValue(value: SerializedValue, handles: any[] | undefined): any {
return innerParseSerializedValue(value, handles, new Map());
@ -34,23 +35,28 @@ function innerParseSerializedValue(value: SerializedValue, handles: any[] | unde
return value.b;
}
if (value.v !== undefined) {
if (value.v === 'undefined') {
return undefined;
}
if (value.v === 'null') {
return null;
}
if (value.v === 'NaN') {
return NaN;
}
if (value.v === 'Infinity') {
return Infinity;
}
if (value.v === '-Infinity') {
return -Infinity;
}
if (value.v === '-0') {
return -0;
switch (value.v) {
case 'undefined': {
return undefined;
}
case 'null': {
return null;
}
case 'NaN': {
return NaN;
}
case 'Infinity': {
return Infinity;
}
case '-Infinity': {
return -Infinity;
}
case '-0': {
return -0;
}
default: {
return assertUnreachable(value.v);
}
}
}
if (value.d !== undefined) {

View file

@ -79,6 +79,7 @@ export class PipeTransport {
_dispatch(buffer: Buffer) {
this._data = Buffer.concat([this._data, buffer]);
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
while (true) {
if (!this._bytesLeft && this._data.length < 4) {
// Need more data.

View file

@ -14,6 +14,7 @@
* limitations under the License.
*/
import { assertUnreachableWithError } from '../common/types';
import { isUnderTest } from '../utils';
export class ValidationError extends Error {}
@ -67,28 +68,32 @@ export const tString: Validator = (arg: any, path: string, context: ValidatorCon
throw new ValidationError(`${path}: expected string, got ${typeof arg}`);
};
export const tBinary: Validator = (arg: any, path: string, context: ValidatorContext) => {
if (context.binary === 'fromBase64') {
if (arg instanceof String) {
return Buffer.from(arg.valueOf(), 'base64');
switch (context.binary) {
case 'fromBase64': {
if (arg instanceof String) {
return Buffer.from(arg.valueOf(), 'base64');
}
if (typeof arg === 'string') {
return Buffer.from(arg, 'base64');
}
throw new ValidationError(`${path}: expected base64-encoded buffer, got ${typeof arg}`);
}
if (typeof arg === 'string') {
return Buffer.from(arg, 'base64');
case 'toBase64': {
if (!(arg instanceof Buffer)) {
throw new ValidationError(`${path}: expected Buffer, got ${typeof arg}`);
}
return (arg as Buffer).toString('base64');
}
case 'buffer': {
if (!(arg instanceof Buffer)) {
throw new ValidationError(`${path}: expected Buffer, got ${typeof arg}`);
}
return arg;
}
default: {
return assertUnreachableWithError(context.binary, new ValidationError(`Unsupported binary behavior "${context.binary}"`));
}
throw new ValidationError(`${path}: expected base64-encoded buffer, got ${typeof arg}`);
}
if (context.binary === 'toBase64') {
if (!(arg instanceof Buffer)) {
throw new ValidationError(`${path}: expected Buffer, got ${typeof arg}`);
}
return (arg as Buffer).toString('base64');
}
if (context.binary === 'buffer') {
if (!(arg instanceof Buffer)) {
throw new ValidationError(`${path}: expected Buffer, got ${typeof arg}`);
}
return arg;
}
throw new ValidationError(`Unsupported binary behavior "${context.binary}"`);
};
export const tUndefined: Validator = (arg: any, path: string, context: ValidatorContext) => {
if (Object.is(arg, undefined)) {
@ -156,6 +161,7 @@ export const tChannel = (names: '*' | string[]): Validator => {
export const tType = (name: string): Validator => {
return (arg: any, path: string, context: ValidatorContext) => {
const v = scheme[name];
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (!v) {
throw new ValidationError(path + ': unknown type "' + name + '"');
}

View file

@ -28,6 +28,7 @@ import { DebugControllerDispatcher } from '../server/dispatchers/debugController
import { startProfiling, stopProfiling } from '../utils';
import { monotonicTime } from '../utils';
import { debugLogger } from '../utils/debugLogger';
import { assertUnreachableWithError } from '../common/types';
export type ClientType = 'controller' | 'launch-browser' | 'reuse-browser' | 'pre-launched-browser-or-android';
@ -108,16 +109,20 @@ export class PlaywrightConnection {
this._root = new RootDispatcher(this._dispatcherConnection, async (scope, options) => {
await startProfiling();
if (clientType === 'reuse-browser') {
return await this._initReuseBrowsersMode(scope);
switch (clientType) {
case 'reuse-browser': {
return await this._initReuseBrowsersMode(scope);
}
case 'pre-launched-browser-or-android': {
return this._preLaunched.browser ? await this._initPreLaunchedBrowserMode(scope) : await this._initPreLaunchedAndroidMode(scope);
}
case 'launch-browser': {
return await this._initLaunchBrowserMode(scope, options);
}
default: {
return assertUnreachableWithError(clientType, new Error('Unsupported client type: ' + clientType));
}
}
if (clientType === 'pre-launched-browser-or-android') {
return this._preLaunched.browser ? await this._initPreLaunchedBrowserMode(scope) : await this._initPreLaunchedAndroidMode(scope);
}
if (clientType === 'launch-browser') {
return await this._initLaunchBrowserMode(scope, options);
}
throw new Error('Unsupported client type: ' + clientType);
});
}
@ -233,9 +238,6 @@ export class PlaywrightConnection {
await context.stopPendingOperations('Connection closed');
}
}
if (!browser.contexts()) {
await browser.close({ reason: 'Connection terminated' });
}
}
});

View file

@ -334,7 +334,7 @@ export class AndroidDevice extends SdkObject {
const artifactsDir = await fs.promises.mkdtemp(ARTIFACTS_FOLDER);
const cleanupArtifactsDir = async () => {
const errors = await removeFolders([artifactsDir]);
for (let i = 0; i < (errors || []).length; ++i) {
for (let i = 0; i < errors.length; ++i) {
debug('pw:android')(`exception while removing ${artifactsDir}: ${errors[i]}`);
}
};
@ -438,6 +438,7 @@ export class AndroidDevice extends SdkObject {
}
const pkg = await this._extractPkg(pid);
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (this._isClosed) {
return;
}

View file

@ -159,9 +159,9 @@ export class BidiBrowser extends Browser {
}
return;
}
let context = this._contexts.get(event.userContext);
let context: BidiBrowserContext | undefined | null = this._contexts.get(event.userContext);
if (!context) {
context = this._defaultContext as BidiBrowserContext;
context = this._defaultContext as BidiBrowserContext | null;
}
if (!context) {
return;
@ -240,6 +240,7 @@ export class BidiBrowserContext extends BrowserContext {
httpOnly: c.httpOnly,
secure: c.secure,
expires: c.expiry ?? -1,
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
sameSite: c.sameSite ? fromBidiSameSite(c.sameSite) : 'None',
};
return copy;

View file

@ -14,6 +14,7 @@
* limitations under the License.
*/
import { assertUnreachableWithError } from 'playwright-core/lib/common/types';
import { parseEvaluationResultValue } from '../isomorphic/utilityScriptSerializers';
import * as js from '../javascript';
import type { BidiSession } from './bidiConnection';
@ -51,13 +52,17 @@ export class BidiExecutionContext implements js.ExecutionContextDelegate {
awaitPromise: true,
userActivation: true,
});
if (response.type === 'success') {
return BidiDeserializer.deserialize(response.result);
switch (response.type) {
case 'success': {
return BidiDeserializer.deserialize(response.result);
}
case 'exception': {
throw new js.JavaScriptErrorInEvaluate(response.exceptionDetails.text + '\nFull val: ' + JSON.stringify(response.exceptionDetails));
}
default: {
return assertUnreachableWithError(response, new js.JavaScriptErrorInEvaluate('Unexpected response type: ' + JSON.stringify(response)));
}
}
if (response.type === 'exception') {
throw new js.JavaScriptErrorInEvaluate(response.exceptionDetails.text + '\nFull val: ' + JSON.stringify(response.exceptionDetails));
}
throw new js.JavaScriptErrorInEvaluate('Unexpected response type: ' + JSON.stringify(response));
}
async rawEvaluateHandle(expression: string): Promise<js.ObjectId> {
@ -69,16 +74,20 @@ export class BidiExecutionContext implements js.ExecutionContextDelegate {
awaitPromise: true,
userActivation: true,
});
if (response.type === 'success') {
if ('handle' in response.result) {
return response.result.handle!;
switch (response.type) {
case 'success': {
if ('handle' in response.result) {
return response.result.handle!;
}
throw new js.JavaScriptErrorInEvaluate('Cannot get handle: ' + JSON.stringify(response.result));
}
case 'exception': {
throw new js.JavaScriptErrorInEvaluate(response.exceptionDetails.text + '\nFull val: ' + JSON.stringify(response.exceptionDetails));
}
default: {
return assertUnreachableWithError(response, new js.JavaScriptErrorInEvaluate('Unexpected response type: ' + JSON.stringify(response)));
}
throw new js.JavaScriptErrorInEvaluate('Cannot get handle: ' + JSON.stringify(response.result));
}
if (response.type === 'exception') {
throw new js.JavaScriptErrorInEvaluate(response.exceptionDetails.text + '\nFull val: ' + JSON.stringify(response.exceptionDetails));
}
throw new js.JavaScriptErrorInEvaluate('Unexpected response type: ' + JSON.stringify(response));
}
async evaluateWithArguments(functionDeclaration: string, returnByValue: boolean, utilityScript: js.JSHandle<any>, values: any[], objectIds: string[]): Promise<any> {
@ -95,17 +104,21 @@ export class BidiExecutionContext implements js.ExecutionContextDelegate {
awaitPromise: true,
userActivation: true,
});
if (response.type === 'exception') {
throw new js.JavaScriptErrorInEvaluate(response.exceptionDetails.text + '\nFull val: ' + JSON.stringify(response.exceptionDetails));
}
if (response.type === 'success') {
if (returnByValue) {
return parseEvaluationResultValue(BidiDeserializer.deserialize(response.result));
switch (response.type) {
case 'success': {
if (returnByValue) {
return parseEvaluationResultValue(BidiDeserializer.deserialize(response.result));
}
const objectId = 'handle' in response.result ? response.result.handle : undefined ;
return utilityScript._context.createHandle({ objectId, ...response.result });
}
case 'exception': {
throw new js.JavaScriptErrorInEvaluate(response.exceptionDetails.text + '\nFull val: ' + JSON.stringify(response.exceptionDetails));
}
default: {
return assertUnreachableWithError(response, new js.JavaScriptErrorInEvaluate('Unexpected response type: ' + JSON.stringify(response)));
}
const objectId = 'handle' in response.result ? response.result.handle : undefined ;
return utilityScript._context.createHandle({ objectId, ...response.result });
}
throw new js.JavaScriptErrorInEvaluate('Unexpected response type: ' + JSON.stringify(response));
}
async getProperties(context: js.ExecutionContext, objectId: js.ObjectId): Promise<Map<string, js.JSHandle>> {
@ -134,13 +147,17 @@ export class BidiExecutionContext implements js.ExecutionContextDelegate {
awaitPromise: true,
userActivation: true,
});
if (response.type === 'exception') {
throw new js.JavaScriptErrorInEvaluate(response.exceptionDetails.text + '\nFull val: ' + JSON.stringify(response.exceptionDetails));
switch (response.type) {
case 'success': {
return response.result;
}
case 'exception': {
throw new js.JavaScriptErrorInEvaluate(response.exceptionDetails.text + '\nFull val: ' + JSON.stringify(response.exceptionDetails));
}
default: {
return assertUnreachableWithError(response, new js.JavaScriptErrorInEvaluate('Unexpected response type: ' + JSON.stringify(response)));
}
}
if (response.type === 'success') {
return response.result;
}
throw new js.JavaScriptErrorInEvaluate('Unexpected response type: ' + JSON.stringify(response));
}
}

View file

@ -272,6 +272,7 @@ class BidiRouteImpl implements network.RouteDelegate {
async continue(overrides: types.NormalizedContinueOverrides) {
// Firefox does not update content-length header.
let headers = overrides.headers || this._request.headers();
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (overrides.postData && headers) {
headers = headers.map(header => {
if (header.name.toLowerCase() === 'content-length') {
@ -342,14 +343,17 @@ function toBidiHeaders(headers: types.HeadersArray): bidi.Network.Header[] {
}
export function bidiBytesValueToString(value: bidi.Network.BytesValue): string {
if (value.type === 'string') {
return value.value;
switch (value.type) {
case 'string': {
return value.value;
}
case 'base64': {
return Buffer.from(value.type, 'base64').toString('binary');
}
default: {
return 'unknown value type: ' + (value as any).type;
}
}
if (value.type === 'base64') {
return Buffer.from(value.type, 'base64').toString('binary');
}
return 'unknown value type: ' + (value as any).type;
}
function toBidiSameSite(sameSite?: 'Strict' | 'Lax' | 'None'): bidi.Network.SameSite | undefined {

View file

@ -189,7 +189,7 @@ export class BidiPage implements PageDelegate {
if (url.startsWith('file:') || url.startsWith('data:') || url === 'about:blank') {
// Navigation to file urls doesn't emit network events, so we fire 'commit' event right when navigation is started.
// Doing it in domcontentload would be too late as we'd clear frame tree.
const frame = this._page._frameManager.frame(frameId)!;
const frame = this._page._frameManager.frame(frameId);
if (frame) {
this._page._frameManager.frameCommittedNewDocumentNavigation(frameId, params.url, '', params.navigation!, /* initial */ false);
}
@ -514,6 +514,7 @@ export class BidiPage implements PageDelegate {
return 'error:notconnected';
}
const rects = node.getClientRects();
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (!rects) {
return null;
}

View file

@ -15,6 +15,7 @@ import type * as Bidi from './bidiProtocol';
*/
export class BidiDeserializer {
static deserialize(result: Bidi.Script.RemoteValue): any {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (!result) {
return undefined;
}

View file

@ -215,7 +215,7 @@ export abstract class BrowserContext extends SdkObject {
await this._cancelAllRoutesInFlight();
// Close extra pages early.
let page: Page | undefined = this.pages()[0];
let page = this.pages()[0] as Page | undefined;
const [, ...otherPages] = this.pages();
for (const p of otherPages) {
await p.close(metadata);
@ -391,7 +391,7 @@ export abstract class BrowserContext extends SdkObject {
// Race against BrowserContext.close
await Promise.race([waitForEvent.promise, this._closePromise]);
}
const page = this.possiblyUninitializedPages()[0];
const page = this.possiblyUninitializedPages()[0] as Page | undefined;
if (!page) {
return;
}
@ -593,7 +593,7 @@ export abstract class BrowserContext extends SdkObject {
if (!oldOrigins.size && !newOrigins.size) {
return;
}
let page = this.pages()[0];
let page = this.pages()[0] as Page | undefined;
const internalMetadata = serverSideCallMetadata();
page = page || await this.newPage({

View file

@ -79,7 +79,7 @@ export class Chromium extends BrowserType {
if (!headersMap) {
headersMap = { 'User-Agent': getUserAgent() };
} else if (headersMap && !Object.keys(headersMap).some(key => key.toLowerCase() === 'user-agent')) {
} else if (!Object.keys(headersMap).some(key => key.toLowerCase() === 'user-agent')) {
headersMap['User-Agent'] = getUserAgent();
}

View file

@ -164,7 +164,7 @@ export class CRBrowser extends Browser {
if (!context) {
// TODO: auto attach only to pages from our contexts.
// assert(this._defaultContext);
context = this._defaultContext as CRBrowserContext;
context = this._defaultContext as CRBrowserContext | null;
}
if (targetInfo.type === 'other' && targetInfo.url.startsWith('devtools://devtools') && this._devtools) {

View file

@ -416,7 +416,7 @@ requestPausedSessionInfo!.session._sendMayFail('Fetch.continueRequest', { reques
}
return Buffer.concat(chunks);
};
const timingPayload = responsePayload.timing!;
const timingPayload = responsePayload.timing;
let timing: network.ResourceTiming;
if (timingPayload && !this._responseExtraInfoTracker.servedFromCache(request._requestId)) {
timing = {
@ -828,7 +828,7 @@ class ResponseExtraInfoTracker {
}
private _patchHeaders(info: RequestInfo, index: number) {
const response = info.responses[index];
const response = info.responses[index] as network.Response | undefined;
const requestExtraInfo = info.requestWillBeSentExtraInfo[index];
if (response && requestExtraInfo) {
response.request().setRawRequestHeaders(headersObjectToArray(requestExtraInfo.headers, '\n'));

View file

@ -212,7 +212,7 @@ export class CRPage implements PageDelegate {
private async _go(delta: number): Promise<boolean> {
const history = await this._mainFrameSession._client.send('Page.getNavigationHistory');
const entry = history.entries[history.currentIndex + delta];
const entry = history.entries[history.currentIndex + delta] as Protocol.Page.NavigationEntry | undefined;
if (!entry) {
return false;
}
@ -639,6 +639,7 @@ class FrameSession {
// In this case, we already have a new session for this frame, so events
// in the old session should be ignored.
const session = this._crPage._sessionForFrame(frame);
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
return session && session !== this && !session._swappedIn;
}
@ -1140,6 +1141,7 @@ class FrameSession {
const nodeInfo = await this._client.send('DOM.describeNode', {
objectId: handle._objectId
});
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (!nodeInfo || typeof nodeInfo.node.frameId !== 'string') {
return null;
}
@ -1150,11 +1152,13 @@ class FrameSession {
// document.documentElement has frameId of the owner frame.
const documentElement = await handle.evaluateHandle(node => {
const doc = node as Document;
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (doc.documentElement && doc.documentElement.ownerDocument === doc) {
return doc.documentElement;
}
return node.ownerDocument ? node.ownerDocument.documentElement : null;
});
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (!documentElement) {
return null;
}
@ -1164,6 +1168,7 @@ class FrameSession {
const nodeInfo = await this._client.send('DOM.describeNode', {
objectId: documentElement._objectId
});
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
const frameId = nodeInfo && typeof nodeInfo.node.frameId === 'string' ?
nodeInfo.node.frameId : null;
documentElement.dispose();

View file

@ -37,6 +37,7 @@ export class CSharpLanguageGenerator implements LanguageGenerator {
} else if (mode === 'mstest') {
this.name = 'MSTest';
this.id = 'csharp-mstest';
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
} else if (mode === 'nunit') {
this.name = 'NUnit';
this.id = 'csharp-nunit';

View file

@ -36,6 +36,7 @@ export class JavaLanguageGenerator implements LanguageGenerator {
if (mode === 'library') {
this.name = 'Library';
this.id = 'java';
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
} else if (mode === 'junit') {
this.name = 'JUnit';
this.id = 'java-junit';

View file

@ -240,9 +240,6 @@ export class DebugController extends SdkObject {
await context.close({ reason: 'Browser collected' });
}
}
if (!browser.contexts()) {
await browser.close({ reason: 'Browser collected' });
}
}
}
}

View file

@ -31,6 +31,7 @@ export class ArtifactDispatcher extends Dispatcher<Artifact, channels.ArtifactCh
}
static fromNullable(parentScope: DispatcherScope, artifact: Artifact): ArtifactDispatcher | undefined {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (!artifact) {
return undefined;
}

View file

@ -350,6 +350,7 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel
async harExport(params: channels.BrowserContextHarExportParams): Promise<channels.BrowserContextHarExportResult> {
const artifact = await this._context._harExport(params.harId);
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (!artifact) {
throw new Error('No HAR artifact. Ensure record.harPath is set.');
}

View file

@ -357,6 +357,7 @@ class HarBackend {
private async _harFindResponse(url: string, method: string, headers: HeadersArray, postData: Buffer | undefined): Promise<har.Entry | undefined> {
const harLog = this._harFile.log;
const visited = new Set<har.Entry>();
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
while (true) {
const entries: har.Entry[] = [];
for (const candidate of harLog.entries) {

View file

@ -154,6 +154,7 @@ export class WebSocketRouteDispatcher extends Dispatcher<{ guid: string }, chann
}
function matchesPattern(dispatcher: PageDispatcher | BrowserContextDispatcher, baseURL: string | undefined, url: string) {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
for (const pattern of dispatcher._webSocketInterceptionPatterns || []) {
const urlMatch = pattern.regexSource ? new RegExp(pattern.regexSource, pattern.regexFlags) : pattern.glob;
if (urlMatches(baseURL, url, urlMatch)) {

View file

@ -183,6 +183,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
}
async isIframeElement(): Promise<boolean | 'error:notconnected'> {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
return this.evaluateInUtility(([injected, node]) => node && (node.nodeName === 'IFRAME' || node.nodeName === 'FRAME'), {});
}
@ -497,6 +498,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
}
progress.throwIfAborted(); // Avoid action that has side-effects.
let restoreModifiers: types.KeyboardModifier[] | undefined;
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (options && options.modifiers) {
restoreModifiers = await this._page.keyboard.ensureModifiers(options.modifiers);
}

View file

@ -566,6 +566,7 @@ export abstract class APIRequestContext extends SdkObject {
progress.log(`${options.method} ${url.toString()}`);
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (options.headers) {
for (const [name, value] of Object.entries(options.headers)) {
progress.log(` ${name}: ${value}`);

View file

@ -123,6 +123,7 @@ export class FFBrowser extends Browser {
_onAttachedToTarget(payload: Protocol.Browser.attachedToTargetPayload) {
const { targetId, browserContextId, openerId, type } = payload.targetInfo;
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
assert(type === 'page');
const context = browserContextId ? this._contexts.get(browserContextId)! : this._defaultContext as FFBrowserContext;
assert(context, `Unknown context id:${browserContextId}, _defaultContext: ${this._defaultContext}`);

View file

@ -38,16 +38,20 @@ function toModifiersMask(modifiers: Set<types.KeyboardModifier>): number {
}
function toButtonNumber(button: types.MouseButton): number {
if (button === 'left') {
return 0;
switch (button) {
case 'left': {
return 0;
}
case 'middle': {
return 1;
}
case 'right': {
return 2;
}
default: {
return 0;
}
}
if (button === 'middle') {
return 1;
}
if (button === 'right') {
return 2;
}
return 0;
}
function toButtonsMask(buttons: Set<types.MouseButton>): number {

View file

@ -103,7 +103,7 @@ export class FrameSelectors {
const targetContext = await resolved.frame._mainContext();
const result: Promise<ElementHandle<Element>>[] = [];
for (const property of properties.values()) {
const elementHandle = property.asElement() as ElementHandle<Element>;
const elementHandle = property.asElement() as ElementHandle<Element> | null;
if (elementHandle) {
result.push(adoptIfNeeded(elementHandle, targetContext));
} else {

View file

@ -107,6 +107,7 @@ export class FrameManager {
}
createDummyMainFrameIfNeeded() {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (!this._mainFrame) {
this.frameAttached(kDummyFrameId, null);
}
@ -143,6 +144,7 @@ export class FrameManager {
frameAttached(frameId: string, parentFrameId: string | null | undefined): Frame {
const parentFrame = parentFrameId ? this._frames.get(parentFrameId)! : null;
if (!parentFrame) {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (this._mainFrame) {
// Update frame id to retain frame identity on cross-process navigation.
this._frames.delete(this._mainFrame._id);
@ -880,7 +882,7 @@ export class Frame extends SdkObject {
throw injected.createStacklessError('Element is not attached to the DOM');
}
const elements = injected.querySelectorAll(info.parsed, root || document);
const element: Element | undefined = elements[0];
const element = elements[0] as Element | undefined;
const visible = element ? injected.utils.isElementVisible(element) : false;
let log = '';
if (elements.length > 1) {
@ -970,6 +972,7 @@ export class Frame extends SdkObject {
if (document.doctype) {
retVal = new XMLSerializer().serializeToString(document.doctype);
}
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (document.documentElement) {
retVal += document.documentElement.outerHTML;
}
@ -1083,6 +1086,7 @@ export class Frame extends SdkObject {
let error = null;
script.onerror = e => error = e;
document.head.appendChild(script);
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (error) {
throw error;
}
@ -1864,6 +1868,7 @@ export class Frame extends SdkObject {
}
// Clean Service Workers
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
const registrations = navigator.serviceWorker ? await navigator.serviceWorker.getRegistrations() : [];
await Promise.all(registrations.map(async r => {
// Heuristic for service workers that stalled during main script fetch or importScripts:
@ -1879,6 +1884,7 @@ export class Frame extends SdkObject {
}));
// Clean IndexedDB
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
for (const db of await indexedDB.databases() || []) {
// Do not wait for the callback - it is called on timer in Chromium (slow).
if (db.name) {

View file

@ -202,6 +202,7 @@ function normalizeStringChildren(rootA11yNode: AriaNode) {
const visit = (ariaNode: AriaNode) => {
const normalizedChildren: (AriaNode | string)[] = [];
const buffer: string[] = [];
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
for (const child of ariaNode.children || []) {
if (typeof child === 'string') {
buffer.push(child);
@ -296,6 +297,7 @@ function matchesNode(node: AriaNode | string, template: AriaTemplateNode, depth:
if (!matchesName(node.name, template)) {
return false;
}
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (!containsList(node.children || [], template.children || [], depth)) {
return false;
}
@ -335,6 +337,7 @@ function matchesNodeDeep(root: AriaNode, template: AriaTemplateNode, collectAll:
if (typeof node === 'string') {
return false;
}
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
for (const child of node.children || []) {
if (visit(child)) {
return true;
@ -414,6 +417,7 @@ export function renderAriaTree(ariaNode: AriaNode, options?: { mode?: 'raw' | 'r
}
} else {
lines.push(escapedKey + ':');
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
for (const child of ariaNode.children || []) {
visit(child, ariaNode, indent + ' ');
}
@ -422,6 +426,7 @@ export function renderAriaTree(ariaNode: AriaNode, options?: { mode?: 'raw' | 'r
if (ariaNode.role === 'fragment') {
// Render fragment.
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
for (const child of ariaNode.children || []) {
visit(child, ariaNode, '');
}

View file

@ -157,6 +157,7 @@ export class ClockController {
}
let firstException: Error | undefined;
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
while (true) {
const result = await this._callFirstTimer(to);
if (!result.timerFound) {
@ -428,6 +429,7 @@ export class ClockController {
isPaused = false;
} else if (type === 'setFixedTime') {
this._innerSetFixedTime(asWallTime(param!));
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
} else if (type === 'setSystemTime') {
this._innerSetTime(asWallTime(param!));
}

View file

@ -43,6 +43,7 @@ export function parentElementOrShadowHost(element: Element): Element | undefined
if (!element.parentNode) {
return;
}
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (element.parentNode.nodeType === 11 /* Node.DOCUMENT_FRAGMENT_NODE */ && (element.parentNode as ShadowRoot).host) {
return (element.parentNode as ShadowRoot).host;
}
@ -80,6 +81,7 @@ export function closestCrossShadow(element: Element | undefined, css: string, sc
}
export function getElementComputedStyle(element: Element, pseudo?: string): CSSStyleDeclaration | undefined {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
return element.ownerDocument && element.ownerDocument.defaultView ? element.ownerDocument.defaultView.getComputedStyle(element, pseudo) : undefined;
}
@ -94,6 +96,7 @@ export function isElementStyleVisibilityVisible(element: Element, style?: CSSSty
// All the browser implement it, but WebKit has a bug which prevents us from using it:
// https://bugs.webkit.org/show_bug.cgi?id=264733
// @ts-ignore
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (Element.prototype.checkVisibility && browserNameForWorkarounds !== 'webkit') {
if (!element.checkVisibility()) {
return false;

View file

@ -92,6 +92,7 @@ export class Highlight {
install() {
// NOTE: document.documentElement can be null: https://github.com/microsoft/TypeScript/issues/50078
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (this._injectedScript.document.documentElement && !this._injectedScript.document.documentElement.contains(this._glassPaneElement)) {
this._injectedScript.document.documentElement.appendChild(this._glassPaneElement);
}

View file

@ -271,11 +271,6 @@ export class InjectedScript {
throw this.createStacklessError('Node is not queryable.');
}
if (selector.capture !== undefined) {
// We should have handled the capture above.
throw this.createStacklessError('Internal error: there should not be a capture in the selector.');
}
// Workaround so that ":scope" matches the ShadowRoot.
// This is, unfortunately, because an ElementHandle can point to any Node (including ShadowRoot/Document/etc),
// and not just to an Element, and we support various APIs on ElementHandle like "textContent()".
@ -531,6 +526,7 @@ export class InjectedScript {
}
describeIFrameStyle(iframe: Element): 'error:notconnected' | 'transformed' | { left: number, top: number } {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (!iframe.ownerDocument || !iframe.ownerDocument.defaultView) {
return 'error:notconnected';
}
@ -687,6 +683,7 @@ export class InjectedScript {
return !disabled && !readonly;
}
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (state === 'checked' || state === 'unchecked') {
const need = state === 'checked';
const checked = getChecked(element, false);
@ -718,6 +715,7 @@ export class InjectedScript {
}
let matches = true;
if (optionToSelect.valueOrLabel !== undefined) {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
matches = matches && (optionToSelect.valueOrLabel === option.value || optionToSelect.valueOrLabel === option.label);
}
if (optionToSelect.value !== undefined) {
@ -835,6 +833,7 @@ export class InjectedScript {
}
const { activeElement, isFocused: wasFocused } = this._activelyFocused(node);
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if ((node as HTMLElement).isContentEditable && !wasFocused && activeElement && (activeElement as HTMLElement | SVGElement).blur) {
// Workaround the Firefox bug where focusing the element does not switch current
// contenteditable to the new element. However, blurring the previous one helps.
@ -899,7 +898,7 @@ export class InjectedScript {
// Get all component roots leading to the target element.
// Go from the bottom to the top to make it work with closed shadow roots.
let parentElement = targetElement;
let parentElement = targetElement as Element | undefined;
while (parentElement) {
const root = enclosingShadowRootOrDocument(parentElement);
if (!root) {
@ -1054,10 +1053,12 @@ export class InjectedScript {
}
// Determine the event point. Note that Firefox does not always have window.TouchEvent.
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
const point = (!!this.window.TouchEvent && (event instanceof this.window.TouchEvent)) ? event.touches[0] : (event as MouseEvent | PointerEvent);
// Check that we hit the right element at the first event, and assume all
// subsequent events will be fine.
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (result === undefined && point) {
result = this.expectHitTarget({ x: point.clientX, y: point.clientY }, element);
}
@ -1265,6 +1266,8 @@ export class InjectedScript {
// New documentElement - let's check whether listeners are still here.
seenEvent = false;
this.window.dispatchEvent(new CustomEvent(customEventName));
// TODO: Dead code?
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (seenEvent) {
return;
}

View file

@ -128,12 +128,13 @@ const kImplicitRoleByTagName: { [tagName: string]: (e: Element) => AriaRole | nu
}
if (['email', 'tel', 'text', 'url', ''].includes(type)) {
// https://html.spec.whatwg.org/multipage/input.html#concept-input-list
const list = getIdRefs(e, e.getAttribute('list'))[0];
const list = getIdRefs(e, e.getAttribute('list'))[0] as Element | undefined;
return (list && elementSafeTagName(list) === 'DATALIST') ? 'combobox' : 'textbox';
}
if (type === 'hidden') {
return null;
}
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
return inputTypeToRole[type] || 'textbox';
},
'INS': () => 'insertion',
@ -206,10 +207,11 @@ function getImplicitAriaRole(element: Element): AriaRole | null {
}
// Inherit presentation role when required.
// https://www.w3.org/TR/wai-aria-1.2/#conflict_resolution_presentation_none
let ancestor: Element | null = element;
let ancestor = element as Element | null;
while (ancestor) {
const parent = parentElementOrShadowHost(ancestor);
const parents = kPresentationInheritanceParents[elementSafeTagName(ancestor)];
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (!parents || !parent || !parents.includes(elementSafeTagName(parent))) {
break;
}
@ -233,7 +235,7 @@ const validRoles: AriaRole[] = ['alert', 'alertdialog', 'application', 'article'
function getExplicitAriaRole(element: Element): AriaRole | null {
// https://www.w3.org/TR/wai-aria-1.2/#document-handling_author-errors_roles
const roles = (element.getAttribute('role') || '').split(' ').map(role => role.trim());
return roles.find(role => validRoles.includes(role as any)) as AriaRole || null;
return (roles.find(role => validRoles.includes(role as any)) as AriaRole | undefined) || null;
}
function hasPresentationConflictResolution(element: Element, role: string | null) {
@ -661,6 +663,7 @@ function getTextAlternativeInternal(element: Element, options: AccessibleNameOpt
// https://w3c.github.io/html-aam/#button-element-accessible-name-computation
if (!labelledBy && tagName === 'BUTTON') {
options.visitedElements.add(element);
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
const labels = (element as HTMLButtonElement).labels || [];
if (labels.length) {
return getAccessibleNameFromAssociatedLabels(labels, options);
@ -671,6 +674,7 @@ function getTextAlternativeInternal(element: Element, options: AccessibleNameOpt
// https://w3c.github.io/html-aam/#output-element-accessible-name-computation
if (!labelledBy && tagName === 'OUTPUT') {
options.visitedElements.add(element);
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
const labels = (element as HTMLOutputElement).labels || [];
if (labels.length) {
return getAccessibleNameFromAssociatedLabels(labels, options);

View file

@ -20,6 +20,7 @@ import { isElementVisible, parentElementOrShadowHost } from './domUtils';
import { type LayoutSelectorName, layoutSelectorScore } from './layoutSelectorUtils';
import { elementMatchesText, elementText, shouldSkipForTextMatching, type ElementText } from './selectorUtils';
import { normalizeWhiteSpace } from '../../utils/isomorphic/stringUtils';
import { assertUnreachableWithError } from 'playwright-core/lib/common/types';
type QueryContext = {
scope: Element | Document;
@ -280,66 +281,70 @@ export class SelectorEvaluatorImpl implements SelectorEvaluator {
}
return this._cached<boolean>(this._cacheMatchesParents, element, [complex, index, context.scope, context.pierceShadow, context.originalScope], () => {
const { selector: simple, combinator } = complex.simples[index];
if (combinator === '>') {
const parent = parentElementOrShadowHostInContext(element, context);
if (!parent || !this._matchesSimple(parent, simple, context)) {
switch (combinator) {
case '>': {
const parent = parentElementOrShadowHostInContext(element, context);
if (!parent || !this._matchesSimple(parent, simple, context)) {
return false;
}
return this._matchesParents(parent, complex, index - 1, context);
}
case '+': {
const previousSibling = previousSiblingInContext(element, context);
if (!previousSibling || !this._matchesSimple(previousSibling, simple, context)) {
return false;
}
return this._matchesParents(previousSibling, complex, index - 1, context);
}
case '': {
let parent = parentElementOrShadowHostInContext(element, context);
while (parent) {
if (this._matchesSimple(parent, simple, context)) {
if (this._matchesParents(parent, complex, index - 1, context)) {
return true;
}
if (complex.simples[index - 1].combinator === '') {
break;
}
}
parent = parentElementOrShadowHostInContext(parent, context);
}
return false;
}
return this._matchesParents(parent, complex, index - 1, context);
}
if (combinator === '+') {
const previousSibling = previousSiblingInContext(element, context);
if (!previousSibling || !this._matchesSimple(previousSibling, simple, context)) {
case '~': {
let previousSibling = previousSiblingInContext(element, context);
while (previousSibling) {
if (this._matchesSimple(previousSibling, simple, context)) {
if (this._matchesParents(previousSibling, complex, index - 1, context)) {
return true;
}
if (complex.simples[index - 1].combinator === '~') {
break;
}
}
previousSibling = previousSiblingInContext(previousSibling, context);
}
return false;
}
return this._matchesParents(previousSibling, complex, index - 1, context);
}
if (combinator === '') {
let parent = parentElementOrShadowHostInContext(element, context);
while (parent) {
if (this._matchesSimple(parent, simple, context)) {
if (this._matchesParents(parent, complex, index - 1, context)) {
return true;
}
if (complex.simples[index - 1].combinator === '') {
break;
case '>=': {
let parent: Element | undefined = element;
while (parent) {
if (this._matchesSimple(parent, simple, context)) {
if (this._matchesParents(parent, complex, index - 1, context)) {
return true;
}
if (complex.simples[index - 1].combinator === '') {
break;
}
}
parent = parentElementOrShadowHostInContext(parent, context);
}
parent = parentElementOrShadowHostInContext(parent, context);
return false;
}
return false;
}
if (combinator === '~') {
let previousSibling = previousSiblingInContext(element, context);
while (previousSibling) {
if (this._matchesSimple(previousSibling, simple, context)) {
if (this._matchesParents(previousSibling, complex, index - 1, context)) {
return true;
}
if (complex.simples[index - 1].combinator === '~') {
break;
}
}
previousSibling = previousSiblingInContext(previousSibling, context);
default: {
return assertUnreachableWithError(combinator, new Error(`Unsupported combinator "${combinator}"`));
}
return false;
}
if (combinator === '>=') {
let parent: Element | undefined = element;
while (parent) {
if (this._matchesSimple(parent, simple, context)) {
if (this._matchesParents(parent, complex, index - 1, context)) {
return true;
}
if (complex.simples[index - 1].combinator === '') {
break;
}
}
parent = parentElementOrShadowHostInContext(parent, context);
}
return false;
}
throw new Error(`Unsupported combinator "${combinator}"`);
});
}
@ -461,6 +466,7 @@ const scopeEngine: SelectorEngine = {
const actualScope = context.originalScope || context.scope;
if (actualScope.nodeType === 9 /* Node.DOCUMENT_NODE */) {
const root = (actualScope as Document).documentElement;
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
return root ? [root] : [];
}
if (actualScope.nodeType === 1 /* Node.ELEMENT_NODE */) {

View file

@ -193,7 +193,7 @@ function generateSelectorFor(injectedScript: InjectedScript, targetElement: Elem
// This is best theoretically possible candidate from the current parent.
// We use the fact that widening the scope to grand-parent makes any selector
// even less likely to match.
let bestPossibleInParent: SelectorToken[] | null = candidates[0];
let bestPossibleInParent = candidates[0] as SelectorToken[] | null;
if (!bestPossibleInParent) {
return;
}
@ -442,7 +442,7 @@ function cssFallback(injectedScript: InjectedScript, targetElement: Element, opt
bestTokenForLevel = token;
}
const parent = element.parentNode as (Element | ShadowRoot);
const parent = element.parentNode as (Element | ShadowRoot | null);
// Combine class names until unique.
const classes = [...element.classList];

View file

@ -55,6 +55,7 @@ export function matchesAttributePart(value: any, attr: AttributeSelectorPart) {
if (attr.op === '|=') {
return objValue === attrValue || objValue.startsWith(attrValue + '-');
}
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (attr.op === '~=') {
return objValue.split(' ').includes(attrValue);
}
@ -63,6 +64,7 @@ export function matchesAttributePart(value: any, attr: AttributeSelectorPart) {
export function shouldSkipForTextMatching(element: Element | ShadowRoot) {
const document = element.ownerDocument;
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
return element.nodeName === 'SCRIPT' || element.nodeName === 'NOSCRIPT' || element.nodeName === 'STYLE' || document.head && document.head.contains(element);
}

View file

@ -23,6 +23,7 @@ export const XPathEngine: SelectorEngine = {
}
const result: Element[] = [];
const document = root.ownerDocument || root;
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (!document) {
return result;
}

View file

@ -72,6 +72,7 @@ export function source() {
if (Object.is(value, undefined)) {
return undefined;
}
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (typeof value === 'object' && value) {
if ('ref' in value) {
return refs.get(value.ref);
@ -92,6 +93,7 @@ export function source() {
if (value.v === '-Infinity') {
return -Infinity;
}
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (value.v === '-0') {
return -0;
}

View file

@ -90,6 +90,7 @@ export async function syncLocalStorageWithSettings(page: Page, appName: string)
await page.addInitScript(
`(${String((settings: any) => {
// iframes w/ snapshots, etc.
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (location && location.protocol === 'data:') {
return;
}

View file

@ -494,6 +494,7 @@ export class Page extends SdkObject {
return;
}
const mainFrame = this._frameManager.mainFrame();
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (!mainFrame || !mainFrame.pendingDocument()) {
return;
}
@ -619,11 +620,11 @@ export class Page extends SdkObject {
async expectScreenshot(metadata: CallMetadata, options: ExpectScreenshotOptions = {}): Promise<{ actual?: Buffer, previous?: Buffer, diff?: Buffer, errorMessage?: string, log?: string[] }> {
const locator = options.locator;
const rafrafScreenshot = locator ? async (progress: Progress, timeout: number) => {
return await locator.frame.rafrafTimeoutScreenshotElementWithProgress(progress, locator.selector, timeout, options || {});
return await locator.frame.rafrafTimeoutScreenshotElementWithProgress(progress, locator.selector, timeout, options);
} : async (progress: Progress, timeout: number) => {
await this.performActionPreChecks(progress);
await this.mainFrame().rafrafTimeout(timeout);
return await this._screenshotter.screenshotPage(progress, options || {});
return await this._screenshotter.screenshotPage(progress, options);
};
const comparator = getComparator('image/png');
@ -632,7 +633,7 @@ export class Page extends SdkObject {
return { errorMessage: '"not" matcher requires expected result' };
}
try {
const format = validateScreenshotOptions(options || {});
const format = validateScreenshotOptions(options);
if (format !== 'png') {
throw new Error('Only PNG screenshots are supported');
}
@ -667,6 +668,7 @@ export class Page extends SdkObject {
progress.log(` generating new stable screenshot expectation`);
}
let isFirstIteration = true;
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
while (true) {
progress.throwIfAborted();
if (this.isClosed()) {

View file

@ -63,7 +63,7 @@ export class Recorder implements InstrumentationListener, IRecorder {
}
static show(codegenMode: 'actions' | 'trace-events', context: BrowserContext, recorderAppFactory: IRecorderAppFactory, params: channels.BrowserContextEnableRecorderParams): Promise<Recorder> {
let recorderPromise = (context as any)[recorderSymbol] as Promise<Recorder>;
let recorderPromise = (context as any)[recorderSymbol] as Promise<Recorder> | undefined;
if (!recorderPromise) {
recorderPromise = Recorder._create(codegenMode, context, recorderAppFactory, params);
(context as any)[recorderSymbol] = recorderPromise;
@ -135,6 +135,7 @@ export class Recorder implements InstrumentationListener, IRecorder {
this._contextRecorder.clearScript();
return;
}
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (data.event === 'runTask') {
this._contextRecorder.runTask(data.params.task);
return;

View file

@ -105,6 +105,7 @@ function iterablePump(): ChunkIterator {
const iterable = (async function* () {
const reader = stream.getReader();
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
while (true) {
const { done, value } = await reader.read();
if (done) {

View file

@ -283,13 +283,14 @@ export class ContextRecorder extends EventEmitter {
export async function generateFrameSelector(frame: Frame): Promise<string[]> {
const selectorPromises: Promise<string>[] = [];
while (frame) {
const parent = frame.parentFrame();
let currentFrame = frame as Frame | undefined;
while (currentFrame) {
const parent = currentFrame.parentFrame();
if (!parent) {
break;
}
selectorPromises.push(generateFrameSelectorInParent(parent, frame));
frame = parent;
selectorPromises.push(generateFrameSelectorInParent(parent, currentFrame));
currentFrame = parent;
}
const result = await Promise.all(selectorPromises);
return result.reverse();
@ -299,6 +300,7 @@ async function generateFrameSelectorInParent(parent: Frame, frame: Frame): Promi
const result = await raceAgainstDeadline(async () => {
try {
const frameElement = await frame.frameElement();
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (!frameElement || !parent) {
return;
}

View file

@ -91,7 +91,7 @@ export class RecorderCollection extends EventEmitter {
if (signal.name === 'navigation' && frame._page.mainFrame() === frame) {
const timestamp = monotonicTime();
const lastAction = this._actions[this._actions.length - 1];
const lastAction = this._actions[this._actions.length - 1] as actions.ActionInContext | undefined;
const signalThreshold = isUnderTest() ? 500 : 5000;
let generateGoto = false;

View file

@ -98,7 +98,7 @@ export function callMetadataForAction(pageAliases: Map<Page, string>, actionInCo
export function collapseActions(actions: actions.ActionInContext[]): actions.ActionInContext[] {
const result: actions.ActionInContext[] = [];
for (const action of actions) {
const lastAction = result[result.length - 1];
const lastAction = result[result.length - 1] as actions.ActionInContext | undefined;
const isSameAction = lastAction && lastAction.action.name === action.action.name && lastAction.frame.pageAlias === action.frame.pageAlias && lastAction.frame.framePath.join('|') === action.frame.framePath.join('|');
const isSameSelector = lastAction && 'selector' in lastAction.action && 'selector' in action.action && action.action.selector === lastAction.action.selector;
const shouldMerge = isSameAction && (action.action.name === 'navigate' || (action.action.name === 'fill' && isSameSelector));

View file

@ -138,7 +138,7 @@ function getDownloadProgress(): OnProgressCallback {
}
function getAnimatedDownloadProgress(): OnProgressCallback {
let progressBar: ProgressBar;
let progressBar: ProgressBar | undefined;
let lastDownloadedBytes = 0;
return (downloadedBytes: number, totalBytes: number) => {

View file

@ -188,6 +188,7 @@ export class Screenshotter {
private async _fullPageSize(progress: Progress): Promise<types.Size> {
const fullPageSize = await this._page.mainFrame().waitForFunctionValueInUtility(progress, () => {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (!document.body || !document.documentElement) {
return null;
}
@ -297,6 +298,7 @@ export class Screenshotter {
return cleanup;
}
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
await Promise.all((options.mask || []).map(async ({ frame, selector }) => {
const pair = await frame.selectors.resolveFrameForSelector(selector);
if (pair) {
@ -378,6 +380,7 @@ export function validateScreenshotOptions(options: ScreenshotOptions): 'png' | '
// options.type takes precedence over inferring the type from options.path
// because it may be a 0-length file with no extension created beforehand (i.e. as a temp file).
if (options.type) {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
assert(options.type === 'png' || options.type === 'jpeg', 'Unknown options.type value: ' + options.type);
format = options.type;
}

View file

@ -91,6 +91,7 @@ export class Selectors {
});
return {
parsed,
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
world: needsMainWorld ? 'main' as const : 'utility' as const,
strict,
};

View file

@ -113,7 +113,7 @@ export class Snapshotter {
// In each frame, in a non-stalling manner, capture the snapshots.
const snapshots = page.frames().map(async frame => {
const data = await frame.nonStallingRawEvaluateInExistingMainContext(expression).catch(e => debugLogger.log('error', e)) as SnapshotData;
const data = await frame.nonStallingRawEvaluateInExistingMainContext(expression).catch(e => debugLogger.log('error', e)) as SnapshotData | undefined;
// Something went wrong -> bail out, our snapshots are best-efforty.
if (!data || !this._started) {
return;

View file

@ -133,6 +133,7 @@ export function frameSnapshotStreamer(snapshotStreamer: string, removeNoScript:
// New documentElement - let's check whether listeners are still here.
seenEvent = false;
window.dispatchEvent(new CustomEvent(customEventName));
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (!seenEvent) {
// Listener did not fire. Reattach the listener and notify.
window.addEventListener(customEventName, handleCustomEvent);
@ -163,7 +164,7 @@ export function frameSnapshotStreamer(snapshotStreamer: string, removeNoScript:
}
private _interceptNativeMethod(obj: any, method: string, cb: (thisObj: any, result: any) => void) {
const native = obj[method] as Function;
const native = obj[method] as Function | undefined;
if (!native) {
return;
}
@ -175,7 +176,7 @@ export function frameSnapshotStreamer(snapshotStreamer: string, removeNoScript:
}
private _interceptNativeAsyncMethod(obj: any, method: string, cb: (thisObj: any, result: any) => void) {
const native = obj[method] as Function;
const native = obj[method] as Function | undefined;
if (!native) {
return;
}
@ -436,11 +437,9 @@ export function frameSnapshotStreamer(snapshotStreamer: string, removeNoScript:
const visitChildStyleSheet = (child: CSSStyleSheet) => {
const snapshot = visitStyleSheet(child);
if (snapshot) {
result.push(snapshot.n);
expectValue(child);
equals = equals && snapshot.equals;
}
result.push(snapshot.n);
expectValue(child);
equals = equals && snapshot.equals;
};
if (nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
@ -482,6 +481,7 @@ export function frameSnapshotStreamer(snapshotStreamer: string, removeNoScript:
expectValue(value);
attrs[kBoundingRectAttribute] = value;
}
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if ((element as HTMLElement).popover && (element as HTMLElement).matches && (element as HTMLElement).matches(':popover-open')) {
const value = 'true';
expectValue(kPopoverOpenAttribute);
@ -626,6 +626,7 @@ export function frameSnapshotStreamer(snapshotStreamer: string, removeNoScript:
};
let html: NodeSnapshot;
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (document.documentElement) {
const { n } = visitNode(document.documentElement)!;
html = n;

View file

@ -405,9 +405,7 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps
const error = await this._fs.syncAndGetError();
this._isStopping = false;
if (this._state) {
this._state.recording = false;
}
this._state.recording = false;
// IMPORTANT: no awaits after this point, to make sure recording state is correct.

View file

@ -118,6 +118,7 @@ export class WebSocketTransport implements ConnectionTransport {
if (result.redirect) {
// Strip authorization headers from the redirected request.
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
const newHeaders = Object.fromEntries(Object.entries(headers || {}).filter(([name]) => {
return !name.includes('access-key') && name.toLowerCase() !== 'authorization';
}));

View file

@ -164,7 +164,7 @@ export class WKBrowser extends Browser {
context = this._contexts.get(event.browserContextId) || null;
}
if (!context) {
context = this._defaultContext as WKBrowserContext;
context = this._defaultContext as WKBrowserContext | null;
}
if (!context) {
return;

View file

@ -198,6 +198,7 @@ export class WKPage implements PageDelegate {
promises.push(this.updateUserAgent());
}
const emulatedMedia = this._page.emulatedMedia();
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (emulatedMedia.media || emulatedMedia.colorScheme || emulatedMedia.reducedMotion || emulatedMedia.forcedColors) {
promises.push(WKPage._setEmulateMedia(session, emulatedMedia.media, emulatedMedia.colorScheme, emulatedMedia.reducedMotion, emulatedMedia.forcedColors));
}
@ -274,6 +275,7 @@ export class WKPage implements PageDelegate {
this._pageProxySession.dispose();
eventsHelper.removeEventListeners(this._sessionListeners);
eventsHelper.removeEventListeners(this._eventListeners);
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (this._session) {
this._session.dispose();
}
@ -1038,6 +1040,7 @@ export class WKPage implements PageDelegate {
frameId: frame._id,
executionContextId: ((context as any)[contextDelegateSymbol] as WKExecutionContext)._contextId
});
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (!result || result.object.subtype === 'null') {
throw new Error('Frame has been detached.');
}

View file

@ -60,14 +60,14 @@ export class WKWorkers {
});
}),
eventsHelper.addEventListener(session, 'Worker.dispatchMessageFromWorker', (event: Protocol.Worker.dispatchMessageFromWorkerPayload) => {
const workerSession = this._workerSessions.get(event.workerId)!;
const workerSession = this._workerSessions.get(event.workerId);
if (!workerSession) {
return;
}
workerSession.dispatchMessage(JSON.parse(event.message));
}),
eventsHelper.addEventListener(session, 'Worker.workerTerminated', (event: Protocol.Worker.workerTerminatedPayload) => {
const workerSession = this._workerSessions.get(event.workerId)!;
const workerSession = this._workerSessions.get(event.workerId);
if (!workerSession) {
return;
}

View file

@ -45,6 +45,7 @@ export function compareBuffersOrStrings(actualBuffer: Buffer | string, expectedB
if (typeof actualBuffer === 'string') {
return compareText(actualBuffer, expectedBuffer);
}
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (!actualBuffer || !(actualBuffer instanceof Buffer)) {
return { errorMessage: 'Actual result should be a Buffer or a string.' };
}

View file

@ -23,7 +23,7 @@ export function headersObjectToArray(headers: HeadersObject, separator?: string,
}
const result: HeadersArray = [];
for (const name in headers) {
const values = headers[name];
const values = headers[name] as string | undefined;
if (values === undefined) {
continue;
}

View file

@ -230,6 +230,7 @@ class KeyParser {
private _readAttributes(result: AriaTemplateRoleNode) {
let errorPos = this._pos;
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
while (true) {
this._skipWhitespace();
if (this._peek() === '[') {

View file

@ -132,6 +132,7 @@ export function parseCSS(selector: string, customNames: Set<string>): { selector
function consumeFunctionArguments(): CSSFunctionArgument[] {
const result = [consumeArgument()];
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
while (true) {
skipWhitespace();
if (!isComma()) {
@ -163,6 +164,7 @@ export function parseCSS(selector: string, customNames: Set<string>): { selector
} else {
result.simples.push({ selector: consumeSimpleSelector(), combinator: '' });
}
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
while (true) {
skipWhitespace();
if (isClauseCombinator()) {

View file

@ -317,6 +317,7 @@ export function tokenize(str1: string): CSSTokenInterface[] {
const consumeComments = function() {
while (next(1) === 0x2f && next(2) === 0x2a) {
consume(2);
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
while (true) {
consume();
if (code === 0x2a && next() === 0x2f) {

View file

@ -16,7 +16,7 @@
import { escapeWithQuotes, normalizeEscapedRegexQuotes, toSnakeCase, toTitleCase } from './stringUtils';
import { type NestedSelectorBody, parseAttributeSelector, parseSelector, stringifySelector } from './selectorParser';
import type { ParsedSelector } from './selectorParser';
import type { ParsedSelector, ParsedSelectorPart } from './selectorParser';
export type Language = 'javascript' | 'python' | 'java' | 'csharp' | 'jsonl';
export type LocatorType = 'default' | 'role' | 'text' | 'label' | 'placeholder' | 'alt' | 'title' | 'test-id' | 'nth' | 'first' | 'last' | 'has-text' | 'has-not-text' | 'has' | 'hasNot' | 'frame' | 'frame-locator' | 'and' | 'or' | 'chain';
@ -177,7 +177,7 @@ function innerAsLocators(factory: LocatorFactory, parsed: ParsedSelector, isFram
continue;
}
const nextPart = parts[index + 1];
const nextPart = parts[index + 1] as ParsedSelectorPart | undefined;
const selectorPart = stringifySelector({ parts: [part] });
const locatorPart = factory.generateLocator(base, 'default', selectorPart);

View file

@ -111,6 +111,7 @@ function shiftParams(template: string, sub: number) {
function transform(template: string, params: TemplateParams, testIdAttributeName: string): string {
// Recursively handle filter(has=, hasnot=, sethas(), sethasnot()).
// TODO: handle and(locator), or(locator), locator(locator), locator(has=, hasnot=, sethas(), sethasnot()).
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
while (true) {
const hasMatch = template.match(/filter\(,?(has=|hasnot=|sethas\(|sethasnot\()/);
if (!hasMatch) {

View file

@ -328,6 +328,7 @@ export function parseAttributeSelector(selector: string, allowUnquotedStrings: b
while (!EOL) {
if (next() === '\\') {
source += eat1();
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (EOL) {
syntaxError('parsing regular expression');
}

View file

@ -30,13 +30,13 @@ export function globToRegex(glob: string): RegExp {
continue;
}
if (c === '*') {
const beforeDeep = glob[i - 1];
const beforeDeep = glob[i - 1] as string | undefined;
let starCount = 1;
while (glob[i + 1] === '*') {
starCount++;
i++;
}
const afterDeep = glob[i + 1];
const afterDeep = glob[i + 1] as string | undefined;
const isDeep = starCount > 1 &&
(beforeDeep === '/' || beforeDeep === undefined) &&
(afterDeep === '/' || afterDeep === undefined);

View file

@ -80,17 +80,15 @@ export function httpRequest(params: HTTPRequestParams, onResponse: (r: http.Inco
https.request(options, requestCallback) :
http.request(options, requestCallback);
request.on('error', onError);
if (timeout !== undefined) {
const rejectOnTimeout = () => {
onError(new Error(`Request to ${params.url} timed out after ${timeout}ms`));
request.abort();
};
if (timeout <= 0) {
rejectOnTimeout();
return;
}
request.setTimeout(timeout, rejectOnTimeout);
const rejectOnTimeout = () => {
onError(new Error(`Request to ${params.url} timed out after ${timeout}ms`));
request.abort();
};
if (timeout <= 0) {
rejectOnTimeout();
return;
}
request.setTimeout(timeout, rejectOnTimeout);
request.end(params.data);
}

View file

@ -36,6 +36,7 @@ export async function pollAgainstDeadline<T>(callback: () => Promise<{ continueP
const lastPollInterval = pollIntervals.pop() ?? 1000;
let lastResult: T|undefined;
const wrappedCallback = () => Promise.resolve().then(callback);
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
while (true) {
const time = monotonicTime();
if (deadline && time >= deadline) {

View file

@ -51,13 +51,14 @@ export class ZipFile {
async read(entryPath: string): Promise<Buffer> {
await this._openedPromise;
const entry = this._entries.get(entryPath)!;
const entry = this._entries.get(entryPath);
if (!entry) {
throw new Error(`${entryPath} not found in file ${this._fileName}`);
}
return new Promise((resolve, reject) => {
this._zipFile!.openReadStream(entry, (error, readStream) => {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (error || !readStream) {
reject(error || 'Entry not found');
return;

View file

@ -254,6 +254,7 @@ function vitePlugin(registerSource: string, templateDir: string, buildInfo: Buil
async transform(this: PluginContext, content, id) {
const queryIndex = id.indexOf('?');
const file = queryIndex !== -1 ? id.substring(0, queryIndex) : id;
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (!buildInfo.sources[file]) {
try {
const timestamp = (await fs.promises.stat(file)).mtimeMs;

View file

@ -52,6 +52,7 @@ function __pwRender(value) {
if (isJsxFragment(type)) {
type = __pwReact.Fragment;
}
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
const props = component.props ? __pwRender(component.props) : {};
const key = component.key ? __pwRender(component.key) : undefined;
const { children, ...propsWithoutChildren } = props;

View file

@ -39,6 +39,7 @@ function __pwRender(value) {
return window.__pwTransformObject(value, v => {
if (isJsxComponent(v)) {
const component = v;
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
const props = component.props ? __pwRender(component.props) : {};
const key = component.key ? __pwRender(component.key) : undefined;
const { children, ...propsWithoutChildren } = props;

View file

@ -112,6 +112,7 @@ window.playwrightMount = async (component, rootElement, hooksConfig) => {
window.playwrightUnmount = async rootElement => {
const svelteComponent = /** @type {SvelteComponent} */ (rootElement[__pwSvelteComponentKey]);
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (!svelteComponent) {
throw new Error('Component was not mounted');
}
@ -125,6 +126,7 @@ window.playwrightUpdate = async (rootElement, component) => {
}
const svelteComponent = /** @type {SvelteComponent} */ (rootElement[__pwSvelteComponentKey]);
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (!svelteComponent) {
throw new Error('Component was not mounted');
}

View file

@ -42,6 +42,7 @@ const utils = Object.freeze({
});
function getPrototype(obj: object) {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (Object.getPrototypeOf) {
return Object.getPrototypeOf(obj);
}

View file

@ -323,6 +323,7 @@ const makeThrowingMatcher = (
// Try to remove this function from the stack trace frame.
// Guard for some environments (browsers) that do not support this feature.
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (Error.captureStackTrace) {
Error.captureStackTrace(error, throwingMatcher);
}
@ -350,6 +351,7 @@ const makeThrowingMatcher = (
!(error instanceof JestAssertionError) &&
error.name !== 'PrettyFormatPluginError' &&
// Guard for some environments (browsers) that do not support this feature.
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
Error.captureStackTrace
) {
// Try to remove this and deeper functions from the stack trace frame.
@ -373,6 +375,7 @@ const makeThrowingMatcher = (
if (isPromise(potentialResult)) {
const asyncError = new JestAssertionError();
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (Error.captureStackTrace) {
Error.captureStackTrace(asyncError, throwingMatcher);
}
@ -432,6 +435,7 @@ const _validateResult = (result: any) => {
function assertions(expected: number): void {
const error = new Error();
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (Error.captureStackTrace) {
Error.captureStackTrace(error, assertions);
}
@ -444,6 +448,7 @@ function assertions(expected: number): void {
}
function hasAssertions(...args: Array<unknown>): void {
const error = new Error();
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (Error.captureStackTrace) {
Error.captureStackTrace(error, hasAssertions);
}

View file

@ -133,6 +133,7 @@ export async function loadConfig(location: ConfigLocation, overrides?: ConfigCLI
}
function validateConfig(file: string, config: Config) {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (typeof config !== 'object' || !config) {
throw errorWithFile(file, `Configuration file must export a single object`);
}
@ -239,7 +240,7 @@ function validateConfig(file: string, config: Config) {
}
if ('reportSlowTests' in config && config.reportSlowTests !== undefined && config.reportSlowTests !== null) {
if (!config.reportSlowTests || typeof config.reportSlowTests !== 'object') {
if (typeof config.reportSlowTests !== 'object') {
throw errorWithFile(file, `config.reportSlowTests must be an object`);
}
if (!('max' in config.reportSlowTests) || typeof config.reportSlowTests.max !== 'number' || config.reportSlowTests.max < 0) {
@ -251,7 +252,7 @@ function validateConfig(file: string, config: Config) {
}
if ('shard' in config && config.shard !== undefined && config.shard !== null) {
if (!config.shard || typeof config.shard !== 'object') {
if (typeof config.shard !== 'object') {
throw errorWithFile(file, `config.shard must be an object`);
}
if (!('total' in config.shard) || typeof config.shard.total !== 'number' || config.shard.total < 1) {
@ -278,6 +279,7 @@ function validateConfig(file: string, config: Config) {
}
function validateProject(file: string, project: Project, title: string) {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (typeof project !== 'object' || !project) {
throw errorWithFile(file, `${title} must be an object`);
}
@ -334,7 +336,7 @@ function validateProject(file: string, project: Project, title: string) {
}
if ('use' in project && project.use !== undefined) {
if (!project.use || typeof project.use !== 'object') {
if (typeof project.use !== 'object') {
throw errorWithFile(file, `${title}.use must be an object`);
}
}

View file

@ -213,6 +213,7 @@ export class FixturePool {
}
// If no errors found, iterate over boxed fixtures
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (!hasDependencyErrors) {
for (const name of names) {
const registration = this._registrations.get(name)!;
@ -260,6 +261,7 @@ export class FixturePool {
const signatureSymbol = Symbol('signature');
export function formatPotentiallyInternalLocation(location: Location): string {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
const isUserFixture = location && filterStackFile(location.file);
return isUserFixture ? formatLocation(location) : '<builtin>';
}
@ -317,6 +319,7 @@ function filterOutComments(s: string): string {
if (s[i - 1] === '*' && s[i] === '/') {
commentState = 'none';
}
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
} else if (commentState === 'none') {
if (s[i] === '/' && s[i + 1] === '/') {
commentState = 'singleline';

View file

@ -285,6 +285,7 @@ export class TestCase extends Base implements reporterTypes.TestCase {
}
titlePath(): string[] {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
const titlePath = this.parent ? this.parent.titlePath() : [];
titlePath.push(this.title);
return titlePath;

View file

@ -337,7 +337,7 @@ export const rootTestType = new TestTypeImpl([]);
export function mergeTests(...tests: TestType<any, any>[]) {
let result = rootTestType;
for (const t of tests) {
const testTypeImpl = (t as any)[testTypeSymbol] as TestTypeImpl;
const testTypeImpl = (t as any)[testTypeSymbol] as TestTypeImpl | undefined;
if (!testTypeImpl) {
throw new Error(`mergeTests() accepts "test" functions as parameters.\nDid you mean to call test.extend() with fixtures instead?`);
}

View file

@ -77,6 +77,7 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
handleSIGINT: false,
...launchOptions,
};
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (headless !== undefined) {
options.headless = headless;
}
@ -179,6 +180,7 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
serviceWorkers,
}, use) => {
const options: BrowserContextOptions = {};
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
if (acceptDownloads !== undefined) {
options.acceptDownloads = acceptDownloads;
}
@ -245,6 +247,7 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
if (serviceWorkers !== undefined) {
options.serviceWorkers = serviceWorkers;
}
/* eslint-enable @typescript-eslint/no-unnecessary-condition */
await use({
...contextOptions,
...options,
@ -454,7 +457,7 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
}
// First time we are reusing the context, we should create the page.
let [page] = context.pages();
let page = context.pages()[0] as Page | undefined;
if (!page) {
page = await context.newPage();
}

View file

@ -494,6 +494,7 @@ export class TeleTestCase implements reporterTypes.TestCase {
}
titlePath(): string[] {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
const titlePath = this.parent ? this.parent.titlePath() : [];
titlePath.push(this.title);
return titlePath;

View file

@ -106,7 +106,7 @@ export class TestTree {
for (const test of parentSuite.tests) {
const title = test.title;
let testCaseItem = parentGroup.children.find(t => t.kind !== 'group' && t.title === title) as TestCaseItem;
let testCaseItem = parentGroup.children.find(t => t.kind !== 'group' && t.title === title) as TestCaseItem | undefined;
if (!testCaseItem) {
testCaseItem = {
kind: 'case',
@ -135,9 +135,9 @@ export class TestTree {
status = 'skipped';
} else if (result.status === 'interrupted') {
status = 'none';
} else if (result && test.outcome() !== 'expected') {
} else if (test.outcome() !== 'expected') {
status = 'failed';
} else if (result && test.outcome() === 'expected') {
} else if (test.outcome() === 'expected') {
status = 'passed';
}

Some files were not shown because too many files have changed in this diff Show more