Skip to content

thenbe/neotest-playwright

Repository files navigation

neotest-playwright

A playwright adapter for neotest.

Written in typescript and transpiled to Lua using tstl.

Features

  • 🎭 Discover, run, and parse the output of playwright tests
  • ⌨️ Quick launch test attachments ( 🕵️ trace, 📼 video)
  • 💅 Project selection + persistence
  • ⚙️ On-the-fly presets

Demo

neotest-playwright-demo.mp4

Table of contents

Installation

Using lazyvim:

{
	'nvim-neotest/neotest',
	dependencies = {
		'thenbe/neotest-playwright',
      dependencies = 'nvim-telescope/telescope.nvim',
	},
	config = function()
		require('neotest').setup({
			adapters = {
				require('neotest-playwright').adapter({
					options = {
						persist_project_selection = true,
						enable_dynamic_test_discovery = true,
					},
				}),
			},
		})
	end,
}

Configuration

All configuration options are optional. Default values are shown below.

require('neotest-playwright').adapter({
	options = {
		persist_project_selection = false,

		enable_dynamic_test_discovery = false,

		preset = 'none', -- "none" | "headed" | "debug"

		get_playwright_binary = function()
			return vim.loop.cwd() .. '/node_modules/.bin/playwright'
		end,

		get_playwright_config = function()
			return vim.loop.cwd() .. '/playwright.config.ts'
		end,

		-- Controls the location of the spawned test process. Has no affect on
		-- neither the location of the binary nor the location of the playwright
		-- config file.
		get_cwd = function()
			return vim.loop.cwd()
		end,

		env = {},

		-- Extra args to always passed to playwright. These are merged with any
		-- extra_args passed to neotest's run command.
		extra_args = {},

		-- Filter directories when searching for test files. Useful in large
		-- projects (see performance notes).
		filter_dir = function(name, rel_path, root)
			return name ~= 'node_modules'
		end,

		-- Custom criteria for a file path to be a test file. Useful in large
		-- projects or projects with peculiar tests folder structure. IMPORTANT:
		-- When setting this option, make sure to be as strict as possible. For
		-- example, the pattern should not return true for jpg files that may end up
		-- in your test directory.
		is_test_file = function(file_path)
			-- By default, only returns true if a file contains one of several file
			-- extension patterns. See default implementation here: https://github.com/thenbe/neotest-playwright/blob/53c7c9ad8724a6ee7d708c1224f9ea25fa071b61/src/discover.ts#L25-L47
			local result = file_path:find('%.test%.[tj]sx?$') ~= nil or file_path:find('%.spec%.[tj]sx?$') ~= nil
			-- Alternative example: Match only files that end in `test.ts`
			local result = file_path:find('%.test%.ts$') ~= nil
			-- Alternative example: Match only files that end in `test.ts`, but only if it has ancestor directory `e2e/tests`
			local result = file_path:find('e2e/tests/.*%.test%.ts$') ~= nil
			return result
		end,

		experimental = {
			telescope = {
				-- If true, a telescope picker will be used for `:NeotestPlaywrightProject`.
				-- Otherwise, `vim.ui.select` is used.
				-- In normal mode, `<Tab>` toggles the project under the cursor.
				-- `<CR>` (enter key) applies the selection.
				enabled = false,
				opts = {},
			},
		},
	},
})

Projects

neotest-playwright allows you to conveniently toggle your playwright Projects on and off. To activate (or deactivate) a project, use the :NeotestPlaywrightProject command. neotest-playwright will only include the projects you've activated in any subsequent playwright commands (using the --project flag). Your selection will persist until you either change it with :NeotestPlaywrightProject, or restart neovim.

If you wish, you can choose to persist your project selection across neovim sessions by setting persist_project_selection to true (see example). Selection data is keyed by the project's root directory, meaning you can persist multiple distinct selections across different projects (or git worktrees).

asciicast


Presets

Presets can help you debug your tests on the fly. A preset is just a group of command line flags that come in handy in common scenarios.

To select a preset, use the :NeotestPlaywrightPreset command. Once a preset is selected, it remains active until you either select another preset, clear it by selecting the none preset, or restart neovim.

headed

Applies the following flags: --headed --retries 0 --timeout 0 --workers 1 --max-failures 0

Runs tests in headed mode.

💡 Tip: Use with await page.pause() to open the playwright inspector and debug your locators.

debug

Applies the following flags: --debug

Playwright uses the --debug flag as a shortcut for multiple options. See here for more information.

none

Does not apply any flags. Your tests will run as defined in your playwright.config.ts file.


Dynamic Test Discovery

neotest-playwright can make use of the playwright cli to unlock extra features. Most importantly, the playwright cli provides information about which tests belongs to which project. neotest-playwright will parse this information to display, run, and report the results of tests on a per-project basis.

To enable this, set enable_dynamic_test_discovery to true.

Caveats

This feature works by calling playwright test --list --reporter=json. While this is a relatively fast operation, it does add some overhead. Therefore, neotest-playwright only calls this feature once (when the adapter is first initialized). From then on, neotest-playwright continues to rely on treesitter to track your tests and enhance them with the data previously resolved by the playwright cli. There are times, however, where we want to refresh this data. To remedy this: neotest-playwright exposes a command :NeotestPlaywrightRefresh. This comes in handy in the following scenarios:

  • Adding a new test
  • Renaming a test
  • Changing the project(s) configuration in your playwright.config.ts file

Consumers

Attachment

Displays the attachments for the test under the cursor. Upon selection, the attachment is launched.

neotest-playwright-test-attachment-2.mp4

Consumers Configuration

Requires enable_dynamic_test_discovery = true.

  1. Include the consumer in your neotest setup:
require('neotest').setup({
	consumers = {
		-- add to your list of consumers
		playwright = require('neotest-playwright.consumers').consumers,
	},
})
  1. Add keybinding:
{
	'thenbe/neotest-playwright',
	keys = {
		{
			'<leader>ta',
			function()
				require('neotest').playwright.attachment()
			end,
			desc = 'Launch test attachment',
		},
	},
}

Performance

Use filter_dir option to limit directories to be searched for tests.

---Filter directories when searching for test files
---@async
---@param name string Name of directory
---@param rel_path string Path to directory, relative to root
---@param root string Root directory of project
---@return boolean
filter_dir = function(name, rel_path, root)
	local full_path = root .. '/' .. rel_path

	if root:match('projects/my-large-monorepo') then
		if full_path:match('^packages/site/test') then
			return true
		else
			return false
		end
	else
		return name ~= 'node_modules'
	end
end

Troubleshooting

testDir should be defined in playwright.config.ts.

Error: Project(s) "foo" not found. Available named projects: "bar", "baz"

  • Run :NeotestPlaywrightProject. Once you apply your selection, any old phantom project names will be cleared from the state file.
  • You may also delete the state file manually. Find the state file's path by running :lua =vim.fn.stdpath('data') .. '/neotest-playwright.json'.

Credits