microsoft / playwright

Playwright is a framework for Web Testing and Automation. It allows testing Chromium, Firefox and WebKit with a single API.
https://playwright.dev
Apache License 2.0
66.92k stars 3.67k forks source link

[Bug]: (Playwright-ct) Unexpected Value error for function type for HooksConfig (Pinia Store) #32612

Open nerdrun opened 2 months ago

nerdrun commented 2 months ago

Version

1.47.0

Steps to reproduce

  1. Clone the minimal reproduced repo : https://github.com/nerdrun/playwright-unexpected-value-issue
  2. Run pnpm i and pnpm test-ct
  3. Check the Unexpected Value error message in the console

Expected behavior

Expected behaviour: Any type should be serialized when HooksConfig value is passed to mount(), however innerSerializeValue() doesn't serialize function type field.

store.ts

export type User = {
  name: string;
  age: number;
  toUserString: () => string; // <== function type
};

export const useStore = defineStore('store', () => {
  const user = ref<User>({ name: 'Steve', age: 20, toUserString: () => '' });
  return {
    user,
  };
});

index.ts

import { createTestingPinia } from '@pinia/testing';
import { beforeMount } from '@playwright/experimental-ct-vue/hooks';
import { type StoreState } from 'pinia';
import { useStore } from '@/stores/store';

export type HooksConfig = {
  store?: StoreState<ReturnType<typeof useStore>>;
};

beforeMount<HooksConfig>(async ({ app, hooksConfig }) => {
  const pinia = createTestingPinia({
    initialState: {
      store: hooksConfig?.store,
    },
    stubActions: false,
    createSpy(args) {
      return () => args;
    },
  });
  app.use(pinia);
});

TestPage.ct.ts

import { test, expect } from '@playwright/experimental-ct-vue';
import { type HooksConfig } from 'playwright';
import { type User } from '@/stores/store';
import TestPage from './TestPage.vue';

test.describe('<TestPage />', () => {
  test('Test User', async ({ mount }) => {
    const mockTestUser: User = {
      name: 'Jackson',
      age: 30,
      toUserString: () => {
        return 'Hello';
      },
    };
    const component = await mount<HooksConfig>(TestPage, {
      hooksConfig: { store: { user: mockTestUser } },
    });

    const name = component.getByTestId('name');
    await expect(name).toHaveText('Jackson');
  });
});

Actual behavior

Throw error message Unexpected value'

Additional context

I've debugged this issue and found out the part that caused the issue.

There is a innerSerializeValue() that serializes arguments that is passed via mount() in ...PATH/node_modules/.pnpm/@playwright+experimental-ct-core@1.47.0_@types+node@20.16.5/node_modules/playwright-core/lib/protocol/serializers.js

There is no code to handle function in the conditions.

function innerSerializeValue(value, handleSerializer, visitorInfo) {
  const handle = handleSerializer(value);
  if ('fallThrough' in handle) value = handle.fallThrough;else return handle;
  if (typeof value === 'symbol') return {
    v: 'undefined'
  };
  if (Object.is(value, undefined)) return {
    v: 'undefined'
  };
  if (Object.is(value, null)) return {
    v: 'null'
  };
  if (Object.is(value, NaN)) return {
    v: 'NaN'
  };
  if (Object.is(value, Infinity)) return {
    v: 'Infinity'
  };
  if (Object.is(value, -Infinity)) return {
    v: '-Infinity'
  };
  if (Object.is(value, -0)) return {
    v: '-0'
  };
  if (typeof value === 'boolean') return {
    b: value
  };
  if (typeof value === 'number') return {
    n: value
  };
  if (typeof value === 'string') return {
    s: value
  };
  if (typeof value === 'bigint') return {
    bi: value.toString()
  };
  if (isError(value)) return {
    e: {
      n: value.name,
      m: value.message,
      s: value.stack || ''
    }
  };
  if (isDate(value)) return {
    d: value.toJSON()
  };
  if (isURL(value)) return {
    u: value.toJSON()
  };
  if (isRegExp(value)) return {
    r: {
      p: value.source,
      f: value.flags
    }
  };
  const id = visitorInfo.visited.get(value);
  if (id) return {
    ref: id
  };
  if (Array.isArray(value)) {
    const a = [];
    const id = ++visitorInfo.lastId;
    visitorInfo.visited.set(value, id);
    for (let i = 0; i < value.length; ++i) a.push(innerSerializeValue(value[i], handleSerializer, visitorInfo));
    return {
      a,
      id
    };
  }
  if (typeof value === 'object') {
    const o = [];
    const id = ++visitorInfo.lastId;
    visitorInfo.visited.set(value, id);
    for (const name of Object.keys(value)) o.push({
      k: name,
      v: innerSerializeValue(value[name], handleSerializer, visitorInfo)
    });
    return {
      o,
      id
    };
  }
  throw new Error('Unexpected value');
}

Environment

System:
  OS: macOS 14.6.1
  CPU: (12) arm64 Apple M2 Pro
  Memory: 81.16 MB / 16.00 GB
Binaries:
  Node: 20.16.0 - ~/.asdf/installs/nodejs/20.16.0/bin.node
  npm: 10.8.1 - ~/.asdf/plugins/nodejs/shims/npm
  pnpm: 9.10.0 - /opt/homebrew/bin/pnpm
Languages:
  Bash: 3.2.57 - /bin/bash
npmPackages:
  @playwright/experimental-ct-vue: ^1.47.0 => 1.47.0
bensenescu commented 1 week ago

+1 Did you ever figure out a workaround here?