test-utils.js 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. import { expect, test as testBase } from '@playwright/test';
  2. import fs from 'node:fs/promises';
  3. import path from 'node:path';
  4. import { fileURLToPath } from 'node:url';
  5. import { loadFixture as baseLoadFixture } from '../../../astro/test/test-utils.js';
  6. export const isWindows = process.platform === 'win32';
  7. // Get all test files in directory, assign unique port for each of them so they don't conflict
  8. const testFiles = await fs.readdir(new URL('.', import.meta.url));
  9. const testFileToPort = new Map();
  10. for (let i = 0; i < testFiles.length; i++) {
  11. const file = testFiles[i];
  12. if (file.endsWith('.test.js')) {
  13. testFileToPort.set(file.slice(0, -8), 4000 + i);
  14. }
  15. }
  16. export function loadFixture(inlineConfig) {
  17. if (!inlineConfig?.root) throw new Error("Must provide { root: './fixtures/...' }");
  18. // resolve the relative root (i.e. "./fixtures/tailwindcss") to a full filepath
  19. // without this, the main `loadFixture` helper will resolve relative to `packages/astro/test`
  20. return baseLoadFixture({
  21. ...inlineConfig,
  22. root: fileURLToPath(new URL(inlineConfig.root, import.meta.url)),
  23. server: {
  24. port: testFileToPort.get(path.basename(inlineConfig.root)),
  25. },
  26. });
  27. }
  28. export function testFactory(inlineConfig) {
  29. let fixture;
  30. const test = testBase.extend({
  31. astro: async ({}, use) => {
  32. fixture = fixture || (await loadFixture(inlineConfig));
  33. await use(fixture);
  34. },
  35. });
  36. test.afterEach(() => {
  37. fixture.resetAllFiles();
  38. });
  39. return test;
  40. }
  41. /**
  42. *
  43. * @param {string} page
  44. * @returns {Promise<{message: string, hint: string, absoluteFileLocation: string, fileLocation: string}>}
  45. */
  46. export async function getErrorOverlayContent(page) {
  47. const overlay = await page.waitForSelector('vite-error-overlay', {
  48. strict: true,
  49. timeout: 10 * 1000,
  50. });
  51. expect(overlay).toBeTruthy();
  52. const message = await overlay.$$eval('#message-content', (m) => m[0].textContent);
  53. const hint = await overlay.$$eval('#hint-content', (m) => m[0].textContent);
  54. const [absoluteFileLocation, fileLocation] = await overlay.$$eval('#code header h2', (m) => [
  55. m[0].title,
  56. m[0].textContent,
  57. ]);
  58. return { message, hint, absoluteFileLocation, fileLocation };
  59. }
  60. /**
  61. * Wait for `astro-island` that contains the `el` to hydrate
  62. * @param {import('@playwright/test').Page} page
  63. * @param {import('@playwright/test').Locator} el
  64. */
  65. export async function waitForHydrate(page, el) {
  66. const astroIsland = page.locator('astro-island', { has: el });
  67. const astroIslandId = await astroIsland.last().getAttribute('uid');
  68. await page.waitForFunction(
  69. (selector) => document.querySelector(selector)?.hasAttribute('ssr') === false,
  70. `astro-island[uid="${astroIslandId}"]`
  71. );
  72. }
  73. /**
  74. * Scroll to element manually without making sure the `el` is stable
  75. * @param {import('@playwright/test').Locator} el
  76. */
  77. export async function scrollToElement(el) {
  78. await el.evaluate((node) => {
  79. node.scrollIntoView({ behavior: 'auto' });
  80. });
  81. }
  82. export function prepareTestFactory(opts) {
  83. const test = testFactory(opts);
  84. let devServer;
  85. test.beforeAll(async ({ astro }) => {
  86. devServer = await astro.startDevServer();
  87. });
  88. test.afterAll(async () => {
  89. await devServer.stop();
  90. });
  91. return {
  92. test,
  93. };
  94. }