Skip to main content

write-node-unit-tests

Write Node.js/TypeScript unit tests for the playwright-wrapper layer. Use when: creating new Jest tests, mocking Playwright API calls, testing getters/interaction/browser-control functions in isolation, improving Node.js test coverage.

معلومات المصدر

المستودع
MarketSquare/robotframework-browser
آخر نشاط في المصدر
٣ أكتوبر ٢٠٢٦ في ١١:١٧
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
٦٦٣
التفرعات
١٤٨

خيارات التثبيت

يُحدَّد Prompt الذي يراجع المصدر أولًا بشكل افتراضي. يمكنك التبديل إلى أمر مباشر أو تنزيل نسخة محلية.

مراجعة ملفات المصدر

اقرأ SKILL.md وأي ملفات مرافقة يعرضها SkillsMP قبل أن تقرر التثبيت.

عرض SKILL.md

SKILL.md
تعليمات المصدر · معاينة للقراءة فقط
name
write-node-unit-tests
description
Write Node.js/TypeScript unit tests for the playwright-wrapper layer. Use when: creating new Jest tests, mocking Playwright API calls, testing getters/interaction/browser-control functions in isolation, improving Node.js test coverage.
applyTo
node/playwright-wrapper/__tests__/**
# Write Node.js Unit Tests Node.js unit tests live in `node/playwright-wrapper/__tests__/` and use **Jest + ts-jest**. Tests must be readable and test one thing at a time. Use descriptive `it()` names that read as plain English sentences. Run tests with invoke: ```bash inv utest-node inv utest-node --coverage # generates HTML report at node/coverage/index.html ``` ## File naming Name test files after the source module they test: `getters.ts` → `getters.test.ts`. ## IDE typing for Jest Every test file imports all Jest globals it uses from `@jest/globals`. If a Jest API such as `beforeEach` or `afterEach` is used without being imported, add it to the existing import line: ```typescript import { beforeEach, describe, expect, it } from '@jest/globals'; ``` Do not add a `/// <reference types="jest" />` directive — it is redundant when importing from `@jest/globals` and inconsistent with the rest of the test suite. Keep using the global `jest` object for `jest.mock()`, `jest.fn()`, and `jest.mocked(...)`. Avoid importing `jest` from `@jest/globals` in these tests, because it can lead to worse mock typing in this repo. ## Mocking external module dependencies Use `jest.mock()` at the top of the file (before imports) to replace modules with controlled fakes. ### Mocking `playwright-invoke` `findLocator` is the primary dependency in most getter/interaction functions. Replace it with a `jest.fn()` and keep `exists` real: ```typescript jest.mock('../playwright-invoke', () => ({ findLocator: jest.fn(), exists: jest.requireActual('../playwright-invoke').exists, })); import { findLocator } from '../playwright-invoke'; const mockFindLocator = jest.mocked(findLocator); ``` ### Mocking the logger Suppress log output to keep test output clean: ```typescript jest.mock('../browser_logger', () => ({ logger: { info: jest.fn(), error: jest.fn() }, })); ``` ### Mocking Playwright errors Use the real `errors` export from `playwright` — no mock needed. Throw `new errors.TimeoutError(...)` directly in test stubs. ## Creating mock locators Build a helper that returns an object with all `Locator` methods stubbed. Pass `overrides` to change just one method per test: ```typescript function makeMockLocator(overrides: Partial<{ waitFor: jest.Mock; isVisible: jest.Mock; isEnabled: jest.Mock; getAttribute: jest.Mock; isEditable: jest.Mock; isChecked: jest.Mock; elementHandle: jest.Mock; }> = {}) { return { waitFor: jest.fn().mockResolvedValue(undefined), isVisible: jest.fn().mockResolvedValue(true), isEnabled: jest.fn().mockResolvedValue(true), getAttribute: jest.fn().mockResolvedValue(null), isEditable: jest.fn().mockResolvedValue(true), isChecked: jest.fn().mockResolvedValue(false), elementHandle: jest.fn().mockResolvedValue(makeElementHandle()), ...overrides, } as unknown as Locator; } ``` ## Creating mock gRPC request objects After the ts-proto migration, generated protobuf types are plain interfaces with direct property access — no getter methods. Mock them with a plain object cast to `any`: ```typescript function makeRequest(selector = '#el', strict = false) { return { selector, strict } as any; } ``` ## Asserting on mock interactions Use `toHaveBeenCalledWith` and `toHaveBeenCalledTimes` to verify how mocks were called — this is often the primary assertion in the wrapper layer: ```typescript expect(mockFindLocator).toHaveBeenCalledWith(callOptions, '#el', false); expect(mockFindLocator).toHaveBeenCalledTimes(1); ``` Use `toBe` for primitives and reference identity checks; use `toEqual` for deep object comparison. ## Async error tests When testing rejected promises, call `expect.assertions(n)` at the top of the test. This ensures the test fails if the error path is never reached: ```typescript it('throws when element is not found', async () => { expect.assertions(1); mockFindLocator.mockRejectedValue(new Error('not found')); await expect(getElementStates(makeRequest(), {} as any)).rejects.toThrow('not found'); }); ``` ## Organising tests with nested describe Group related cases under inner `describe` blocks to make large test files navigable: ```typescript describe('getElementStates', () => { describe('when element is visible', () => { it('returns visible state', async () => { ... }); }); describe('when element times out', () => { it('returns detached state', async () => { ... }); }); }); ``` ## Sequencing multiple `evaluate` calls When a function calls `elementHandle.evaluate()` multiple times, use `mockResolvedValueOnce` chaining: ```typescript const elementHandle = { evaluate: jest.fn() .mockResolvedValueOnce(false) // first call: 'selected' in e .mockResolvedValueOnce(true), // second call: document.activeElement === e }; ``` ## Test structure ```typescript describe('functionName', () => { beforeEach(() => { jest.clearAllMocks(); }); it('returns detached state when element times out', async () => { const locator = makeMockLocator({ waitFor: jest.fn().mockRejectedValue(new errors.TimeoutError('Timeout')), }); mockFindLocator.mockResolvedValue(locator); const result = await getElementStates(makeRequest(), {} as any); expect(JSON.parse(result.json)).toBe(2); // detached }); }); ``` ## Things to avoid - Do not add comments or docstrings explaining what the test does — the test name and code should be self-explanatory. - Mock at the boundary closest to your unit. Prefer spying over full module replacement when only one method needs controlling; don't mock modules that aren't exercised by the test. - Do not assert on log messages — they are implementation details. - Do not share mutable mock state between tests; always call `jest.clearAllMocks()` in `beforeEach`.
عرض على GitHub