Repository: microsoft/playwright
File: packages/playwright/src/matchers/matchers.ts (TypeScript)

Code before:
  }, expected, options);
}

export function toHaveURL(
  this: ExpectMatcherStateInternal,
  page: Page,
  expected: string | RegExp | URLPattern | ((url: URL) => boolean),
  options?: { ignoreCase?: boolean; timeout?: number },
) {
  if (isURLPattern(expected))
    return toHaveURLWithPredicate.call(this, page, url => (expected as URLPattern).test(url.href), options);

  // Ports don't support predicates. Keep separate server and client codepaths
  if (typeof expected === 'function')
    return toHaveURLWithPredicate.call(this, page, expected, options);

  const baseURL = (page.context() as any)._options.baseURL;
  expected = typeof expected === 'string' ? constructURLBasedOnBaseURL(baseURL, expected) : expected;
  return toMatchText.call(this, 'toHaveURL', page, 'Page', async (isNot, timeout) => {
    const expectedText = serializeExpectedTextValues([expected], { ignoreCase: options?.ignoreCase });
    return await (page.mainFrame() as FrameEx)._expect('to.have.url', { expectedText, isNot, timeout });
  }, expected, options);
}

// @ts-expect-error globalThis.URLPattern is not in @types/node yet
const isURLPattern = (v: any): v is URLPattern => 'URLPattern' in globalThis && v instanceof globalThis.URLPattern;

export async function toBeOK(
  this: ExpectMatcherStateInternal,
  response: APIResponseEx
) {
  const matcherName = 'toBeOK';
  expectTypes(response, ['APIResponse'], matcherName);

  const contentType = response.headers()['content-type'];
  const isTextEncoding = contentType && isTextualMimeType(contentType);
  const [log, text] = (this.isNot === response.ok()) ? await Promise.all([
    response._fetchLog(),
    isTextEncoding ? response.text() : null
  ]) : [];

  const message = () => formatMatcherMessage(this.utils, {
    isNot: this.isNot,
    promise: this.promise,
    matcherName,
    receiver: 'response',
    expectation: '',
    log,
  }) + (text === null ? '' : `\nResponse text:\n${colors.dim(text?.substring(0, 1000) || '')}`);

  const pass = response.ok();

Review comment (suggestion):
Let's export this from `urlMatch.ts` to avoid too many `ts-expect-error` markers.

Code after:
  }, expected, options);
}

export function toHaveURL(
  this: ExpectMatcherStateInternal,
  page: Page,
  expected: string | RegExp | URLPattern | ((url: URL) => boolean),
  options?: { ignoreCase?: boolean; timeout?: number },
) {
  if (isURLPattern(expected))
    return toHaveURLWithPredicate.call(this, page, url => (expected as URLPattern).test(url.href), options);

  // Ports don't support predicates. Keep separate server and client codepaths
  if (typeof expected === 'function')
    return toHaveURLWithPredicate.call(this, page, expected, options);

  const baseURL = (page.context() as any)._options.baseURL;
  expected = typeof expected === 'string' ? constructURLBasedOnBaseURL(baseURL, expected) : expected;
  return toMatchText.call(this, 'toHaveURL', page, 'Page', async (isNot, timeout) => {
    const expectedText = serializeExpectedTextValues([expected], { ignoreCase: options?.ignoreCase });
    return await (page.mainFrame() as FrameEx)._expect('to.have.url', { expectedText, isNot, timeout });
  }, expected, options);
}

export async function toBeOK(
  this: ExpectMatcherStateInternal,
  response: APIResponseEx
) {
  const matcherName = 'toBeOK';
  expectTypes(response, ['APIResponse'], matcherName);

  const contentType = response.headers()['content-type'];
  const isTextEncoding = contentType && isTextualMimeType(contentType);
  const [log, text] = (this.isNot === response.ok()) ? await Promise.all([
    response._fetchLog(),
    isTextEncoding ? response.text() : null
  ]) : [];

  const message = () => formatMatcherMessage(this.utils, {
    isNot: this.isNot,
    promise: this.promise,
    matcherName,
    receiver: 'response',
    expectation: '',
    log,
  }) + (text === null ? '' : `\nResponse text:\n${colors.dim(text?.substring(0, 1000) || '')}`);

  const pass = response.ok();
  return { message, pass };
}