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.83k stars 3.66k forks source link

[Question] Possible to import ts modules from node_modules? #14303

Open oskarkivra opened 2 years ago

oskarkivra commented 2 years ago

Hi,

We are, at my company, publishing a lot of private typescript npm modules. It is really handy to not compile the typescript files before publishing the module and letting the consumer transpile the code after its need. With tools like webpack, rollup, vite, esbuild this is really easy and has a lot of benefits.

However, when we now want to use a typescript file that is placed in node_modules in a Playwright test we got an error, probably because Playwright does not compile the code inside node_modules. Is this possible to configure?

Example:

// node_modules/my-package/index.ts
export const myNumber: number = 5;

// tests/example.spec.ts
import { test } from "@playwright/test";
import { myNumber } from "my-package";

test("My test", () => {
  console.log(myNumber);
});

Gives the following error when running npx playwright test:

node_modules/my-package/index.ts:1

export const myNumber: number = 5;
^^^^^^

SyntaxError: Unexpected token 'export'

    at Object.compileFunction (node:vm:352:18)
    at wrapSafe (node:internal/modules/cjs/loader:1031:15)

So is this possible to configure or do we have to pre-compile our module?

Thanks

pavelfeldman commented 2 years ago

There is no such thing as a Typescript NPM module. NPM modules are all JavaScript, they can be compiled from Typescript, but they are JavaScript.

You could try using tsconfig.json to resolve your module imports into respective node_modules/my-package folders though. Playwright will pick them up and compile at test time: https://www.typescriptlang.org/tsconfig#paths

oskarkivra commented 2 years ago

Thanks for answering.

I went with the solution to precompile the files on the fly with tsm

node --require tsm ./node_modules/.bin/playwright test
olegper commented 2 years ago

Thanks for answering.

I went with the solution to precompile the files on the fly with tsm

node --require tsm ./node_modules/.bin/playwright test

But in that case it is impossible to run tests through Playwright VS code extension (only through command line)

oskarkivra commented 2 years ago

@olegper True and this does not seem like a waterproof solution. It would be nice to configure playwright what paths to include in the transpiring.

olegper commented 2 years ago

Strange. I wonder why there are not so much people, experiencing that issue with Playwright. Worked perfectly with Cypress though

lesha1201 commented 2 years ago

It would be nice to re-open this issue since it's a valid case where you need to transpile some packages from node_modules.

I have a UI-Kit built using vanilla-extract and it's published in a raw format (plain TypeScript) to leave the build step on the client side. In my project, I use some constants from the source code (e.g. constants for routes) in the E2E tests. If I import them from index.ts that imports other things like utility functions that can use something from that UI-Kit then it complains for TS files from node_modules even though tests doesn't use them. So as a workaround I just import these constants directly but it would be nice to not have this limitation.

oskarkivra commented 2 years ago

@lesha1201 I agree; there is plenty of use cases, and node_modules do not only contain javascript code these days.

I can reopen this and let @pavelfeldman and the rest of the team decide if this should have more attention or not.

RickMeijer commented 2 years ago

Thank you! We are working in a monorepo, and have some shared (private) packages that are typescript only, for convenience sake (to add another use case).

hafley66 commented 1 year ago

To chime in as I was looking to adopt playwright. I have private modules that are published as js with their d.ts files. however they are converted into js+export/import syntax to help tree shaking from bundlers.

We model our endpoints with a formal base class abstraction that holds onto url and input/output types. I wanted to re-use them so i could make a typed helper for page.route to integrate making it easier to mock an endpoint using this abstraction.

This requires importing from the src/ folder, but these files are importing from node modules as well

surfmuggle commented 1 year ago

We have a similar use case to import some common actions (find element, get value) to a helper file

async function Login_user_foo(page) {
    await page.goto('<url-qa-stage>');
    await page.getByLabel('Remember me').check();                
    await page.getByLabel('Password').fill('<user-pass>');    
    await page.getByRole('button', { name: 'Log In to Sandbox' }).click();   
}

export {Login_user_foo};

to reuse them in different tests:

    test('Shopping Cart', async ({ page }) => {
        await Login_user_foo(page);
        await test.step("List shopping cart", async() => {   
            await page.goto('<url-qa-cart>');
            ...
        });
        await test.step("Count cart Items", async() => {
            ...
        });
    });
bensampaio commented 1 year ago

I have the same use case. At my company, we are trying to create a POC for Playwright and this is our main blocker for moving forward with it. We have been looking into this issue for quite a few months now. Is there still no way around this?

I am looking at the build config but it's not clear to me if it could fix this issue. Has any of you tried it?

tbo commented 1 year ago

@bensampaio We have found a workaround by using tsx, which allows us to transpile TypeScript files on the fly.

Instead of using the standard script in package.json:

"test": "playwright test",

You can replace it with the following, which utilizes node --loader tsx:

"test": "node --loader tsx ./node_modules/@playwright/test/cli.js test",

This modification instructs Node.js to use tsx as the loader, which will automatically handle the transpilation of TypeScript files when running your tests. We've tested this in our setup and it works smoothly, eliminating the need for a separate transpilation step.

bensampaio commented 1 year ago

Thanks @tbo! I tried this out but got the following error:

TypeError: Cannot assign to read only property '.mjs' of object '[object Object]'

It comes from playwright/lib/utilsBundleImpl.js:17:874.

Did you also come across this issue?

tbo commented 1 year ago

@bensampaio No, it ran out-of-the-box for us.

bensampaio commented 1 year ago

@pavelfeldman we are basically asking for an option similar to transformIgnorePatterns in Jest, or exclude in webpack and esbuild. This kind of requirement has been a must for any project I worked on in the past 8 years. It is quite common for companies to publish npm packages that are not fully transpiled. There might not be a lot of people on this thread but I can't imagine this isn't a problem for a lot of projects. What's keeping the Playwright team from implementing something like this? Is there something we can do to help?

tbo commented 1 year ago

@bensampaio

Did you also come across this issue?

It turns out that the latest tsx version changed the loader behavior. This should work with the latest tsx and playwright versions:

  "test": "node --import tsx/esm ./node_modules/@playwright/test/cli.js test",
bensampaio commented 12 months ago

@tbo thank you for letting me know! I just gave this a try but this command seems to be specific for Node 20. I tried to replace --import with --loader but it didn't work 😞

I managed to make things work by using the new exports field for our libraries in the package.json. The issue is that jest didn't like this change so I'm still trying to find a way of making everything work 🤷🏻‍♂️

sunilsurana commented 10 months ago

Hi, We are running into same issue. Is this getting looked into? How do we specify the path property in tsconfig to support transpilation in node_modules folder. Can some provide an example?

sunilsurana commented 10 months ago

There is no such thing as a Typescript NPM module. NPM modules are all JavaScript, they can be compiled from Typescript, but they are JavaScript.

You could try using tsconfig.json to resolve your module imports into respective node_modules/my-package folders though. Playwright will pick them up and compile at test time: https://www.typescriptlang.org/tsconfig#paths

Playwright is not tranpiling ts files in node_modules even if we provide it in path for tsconfig for eg Below does not work.

"paths": {
      "tests-utils": [
        "node_modules/tests-utils/src/index.ts"
      ],
}

But if we copy test-utils outside node_modules folder and specify random folder then it works.

"paths": {
      "tests-utils": [
        "random/tests-utils/src/index.ts"
      ],
}
pietschy commented 9 months ago

We'd love to use playwright but this issue has been a dead end for us. We have to control a number of external simulators and systems for our tests so we need both node_modules and other packages in our mono repo to work. We've used cypress in the past but are very keen to move to a promise based solution to simplify our test structure.

I've tried the various workarounds here but haven't had an success.

Using node --import tsx/esm .... we still get "Cannot use import statement outside a module" when trying to import another TS module in our project.

I tried using tsx directly and get the following error:

TypeError: Cannot assign to read only property '.mjs' of object '[object Object]'
    at /Users/andrewpietsch/coolon/projects/mars-v2/frontend/node_modules/.pnpm/playwright@1.40.0/node_modules/playwright/lib/utilsBundleImpl.js:16:843
    at Array.forEach (<anonymous>)
    at Object.Jl (/Users/andrewpietsch/coolon/projects/mars-v2/frontend/node_modules/.pnpm/playwright@1.40.0/node_modules/playwright/lib/utilsBundleImpl.js:16:690)
    at installTransform (/Users/andrewpietsch/coolon/projects/mars-v2/frontend/node_modules/.pnpm/playwright@1.40.0/node_modules/playwright/lib/transform/transform.js:199:46)
    at requireOrImport (/Users/andrewpietsch/coolon/projects/mars-v2/frontend/node_modules/.pnpm/playwright@1.40.0/node_modules/playwright/lib/transform/transform.js:171:30)
    at requireOrImportDefaultObject (/Users/andrewpietsch/coolon/projects/mars-v2/frontend/node_modules/.pnpm/playwright@1.40.0/node_modules/playwright/lib/common/configLoader.js:121:53)
    at ConfigLoader.loadConfigFile (/Users/andrewpietsch/coolon/projects/mars-v2/frontend/node_modules/.pnpm/playwright@1.40.0/node_modules/playwright/lib/common/configLoader.js:94:26)
    at runTests (/Users/andrewpietsch/coolon/projects/mars-v2/frontend/node_modules/.pnpm/playwright@1.40.0/node_modules/playwright/lib/cli.js:118:55)
    at t.<anonymous> (/Users/andrewpietsch/coolon/projects/mars-v2/frontend/node_modules/.pnpm/playwright@1.40.0/node_modules/playwright/lib/cli.js:40:7)

Searching the tsx issue list I found this https://github.com/privatenumber/tsx/issues/230 which notes the cause is another library trying to override the tsx loader. It appears utilsBundleImpl.js:16:843 is doing something similar.

We're using playwright version 1.41.1 and node 20.11.0

Any help to get this working would be greatly appreciated.

pietschy commented 8 months ago

For anyone stumbling across this we managed to get playwright working for our e2e tests using vite/vitest (we're not trying to do component testing). It's not as nice as the native playwright experience but at least it works.

We're using NX so I installed the vite plugin and used their variant of the ts-config-paths but other than that we created a vanilla vite config and are running the vitest from the cli (not using any of the NX executors or generators).

Our e2e tests have a lot of dependencies for driving external apis so we had to do some clean up to to replace old libs that didn't play well with vite, but after all that it's finally running 😮‍💨

path/to/e2e-app/vite.config.ts

/// <reference types="vitest" />
import { defineConfig } from 'vite';

import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';

export default defineConfig(({ mode }) => ({
  plugins: [nxViteTsPaths()],
  test: {
    globals: true,
    setupFiles: ['path/to/e2e-app/src/test-setup.ts'],
    include: ['path/to/e2e-app/src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
    reporters: ['default']
  },
  define: {
    'import.meta.vitest': mode !== 'production',
  },
}));

path/to/e2e/app/src/my-tests.spec.ts

import {afterAll, beforeAll, describe, test} from "vitest";
import {Browser, BrowserContext, chromium, expect, Page} from "@playwright/test";

describe('my tests', () => {
  let page: Page;
  let browser: Browser;
  let context: BrowserContext;

  beforeAll(async () => {
    browser = await chromium.launch({headless: false});
    let context = await browser.newContext();
    page = await context.newPage();
  });

  afterAll(async () => {
    await browser.close();
  });

  test('has title', async () => {
    await page.goto('http://localhost:4221/');
    let locator = page.locator('h1');
    expect(await locator.innerText()).toContain('Hello World');
  });

});

cli

npx vitest watch --ui --threads false -c path/to/e2e-app/vite.config.ts
dorsharonfuse commented 7 months ago

This issue is still relevant at playwright v1.42.1 and Node v20.10.0. Is there any fix or workaround using tsx?

Jrubzjeknf commented 7 months ago

Another sufferer from this issue here. At our company we have a central repo where we create npm modules for customer projects. These projects load large parts of their application from the npm modules, and the tests are parts of that too. However, we're unable to load these tests.

For other readers, using symlinks (hard or soft) to refer to the npm module directory does not seem to work with playwright's ts file loader. We're currently implementing copying the files into a gitignored directory in the customer project as a workaround. It's ugly, but it works for our purposes.

BeejeeDS commented 7 months ago

I've just stumbled upon the same issue in our project.

We have our company wide packages with custom Angular components and controls (comparable to Angular material). For each of those components I've created a TestHarness to allow the developers and testers to write tests without the hassle of finding the right selectors and so on. Only to find out that the harnesses can't be used because of this problem.

Is there any temporary solution for the problem and will this be fixed in an upcoming version of Playwright?