08 · JavaScript/TypeScript для AQA
JavaScript і Node.js: мова, рушії, середовища виконання
Зміст
Ти сідаєш писати перший автотест на Playwright, копіюєш приклад із документації, запускаєш — і бачиш ReferenceError: document is not defined. Хоча в браузері document є завжди. Або навпаки: у тесті працює require('fs'), а той самий рядок у консолі браузера падає. Це не баг і не магія — це наслідок того, що «JavaScript» насправді складається з трьох різних шарів: мови, рушія (engine) і середовища виконання (runtime). Плутанина між ними — джерело половини незрозумілих помилок у новачка в автоматизації.
Ця глава дає карту цих трьох шарів. Вона легка й на співбесідах важить мало, але без неї решта розділу висить у повітрі: ти не зрозумієш, чому тестовий код бачить файлову систему, але не бачить кнопку на сторінці, і де саме виконується той код, який ти пишеш. Розберемося, що таке ECMAScript, чим Node.js відрізняється від браузера, і як усе це запускати руками.
Навіщо AQA саме JavaScript і TypeScript
Мова автоматизації зазвичай не питання смаку — її диктує екосистема інструментів. Провідні тулзи для e2e-тестування вебу (Playwright, CodeceptJS, Cypress, WebdriverIO) написані на JavaScript і його типізованому наднаборі TypeScript, і твої тести — це код тією ж мовою. Можна тестувати на Java чи Python, але тоді ти втрачаєш частину екосистеми: приклади в документації, плагіни, відповіді на Stack Overflow, спільну мову з фронтенд-розробниками.
Другий аргумент — сам продукт. Фронтенд сучасного вебзастосунку — це JavaScript. Коли ти читаєш код сторінки в DevTools, дебажиш, чому не спрацював обробник події, чи розумієш, що застосунок кешує стан у пам'яті, — ти читаєш JS. AQA, який знає мову продукту, заводить точніші баги й швидше знаходить першопричину.
TypeScript додає до JavaScript статичну типізацію (static typing) — перевірку типів до запуску. Для тестового коду це страховка: середовище підказує, що в об'єкта сторінки немає такого методу, ще коли ти друкуєш, а не коли тест падає в CI через годину. Детально TypeScript — у главах /js-ts/typescript-typy-interfeisy-ta-strohyi-rezhym і далі; тут важливо лише, що TS — це надбудова над тією самою мовою, а не окрема мова.
Мова, специфікація і рушій — три різні речі
Почнемо з термінів, які постійно плутають.
ECMAScript (ECMA-262) — це специфікація мови: стандарт, який описує синтаксис, типи даних, вбудовані об'єкти (Array, Object, Promise) і правила виконання. Його розробляє комітет TC39 при організації Ecma International і щороку випускає нову редакцію: ES2015 (вона ж ES6) принесла let/const, класи й стрілочні функції, далі — ES2016, ES2017 (з async/await) і так щороку. Специфікація — це текст, а не програма; сама по собі вона нічого не виконує.
JavaScript — це мова, яка реалізує цей стандарт. На практиці «JavaScript» і «ECMAScript» вживають майже як синоніми, але суворо: ECMAScript — це специфікація, JavaScript — мова, що їй відповідає (плюс історична назва-бренд, права на яку колись належали Oracle). Коли кажуть «нова фіча JS», зазвичай мають на увазі нову редакцію ECMAScript.
Рушій (JavaScript engine) — це вже програма, яка бере твій текст коду й виконує його: парсить, компілює в машинний код і запускає. Різні виробники мають свої рушії:
| Рушій | Хто розробляє | Де працює |
|---|---|---|
| V8 | Chrome, Chromium-браузери, Node.js | |
| SpiderMonkey | Mozilla | Firefox |
| JavaScriptCore | Apple | Safari, WebKit |
Усі вони реалізують той самий стандарт ECMAScript, тому базовий код [1, 2, 3].map(x => x * 2) поводиться в них однаково. Розбіжності починаються не в мові, а в тому, які додаткові можливості дає навколишнє середовище. Саме тому «браузерний баг лише в Safari» — це майже завжди різниця середовища або рушія, а не самої мови.
Читати діаграму знизу вгору: середовище дає API, рушій виконує код, а стандарт описує саму мову. V8 — той самий рушій і в Chrome, і в Node.js; відрізняється те, що навколо нього.
V8: один двигун — два будинки
V8 — це рушій від Google, написаний на C++. Він компілює JavaScript у машинний код під час виконання (JIT-компіляція, just-in-time), тому виконання швидке. Ключове для розуміння: V8 уміє виконувати мову — але сам по собі не вміє ні малювати сторінку, ні читати файли. Він знає Array і Math, але не знає, що таке «клік по кнопці» чи «файл на диску». Усе це дає host-середовище — програма, яка вбудовує в себе V8 і додає власні можливості.
Таких будинків для V8 два, і саме тут криється відповідь на загадку з початку глави.
Браузер вбудовує рушій, щоб оживляти сторінки. Він додає до мови об'єкти для роботи з документом: глобальний window, document, весь DOM (детально — /web/dom-selektory-ta-podii), localStorage, події миші й клавіатури. Цих речей немає в стандарті ECMAScript — їх визначають окремі специфікації (DOM, Web APIs), а браузер реалізує.
Node.js вбудовує той самий V8, щоб виконувати JavaScript поза браузером — на сервері, у CLI-утилітах і, що для нас головне, у тестових ранерах. Node.js створив Ryan Dahl у 2009 році; він поєднує V8 із бібліотекою libuv, яка дає асинхронний ввід-вивід і подієвий цикл (event loop — канонічний розбір у /web/vykonannia-javascript-ta-event-loop). Node додає до мови зовсім інший набір можливостей: доступ до файлової системи, мережі, процесів, змінних оточення.
Ось чому Playwright-тест не бачить document: він виконується не в браузері, а в Node.js. Node запускає твій тестовий код, а той уже командує окремим браузером через протокол. Код тесту й код сторінки живуть у різних середовищах — тому в тесті є require('fs'), але немає document, і навпаки.
Що є в Node.js і чого немає в браузері
Обидва середовища виконують одну мову, але дають різні глобальні об'єкти й API. Ось найважливіші відмінності, які AQA зустрічає першими:
| Можливість | Браузер | Node.js |
|---|---|---|
| Глобальний об'єкт | window | global (обидва мають globalThis) |
DOM: document, елементи сторінки | Є | Немає |
localStorage, sessionStorage | Є | Немає |
Файлова система (fs), шляхи (path) | Немає | Є |
Процес і аргументи (process.argv, process.env) | Немає | Є |
Робота з бінарними даними (Buffer) | Немає (є типізовані масиви) | Є |
Модулі (require / import) | Тільки ES-модулі | CommonJS та ES-модулі |
Практичне значення цієї таблиці для тестів пряме. Змінні оточення через process.env — стандартний спосіб передати в тест базовий URL чи токен, не зашиваючи їх у код. Модуль fs читає тестові дані з файлу або зберігає артефакти. А document і localStorage доступні лише всередині сторінки — дістатися до них із тесту можна тільки виконавши код у контексті браузера (у Playwright це page.evaluate):
import { test, expect } from '@playwright/test';
test('токен лежить у localStorage', async ({ page }) => {
await page.goto(process.env.BASE_URL + '/dashboard'); // process.env — це Node
// а цей колбек виконується вже в браузері, там є localStorage
const token = await page.evaluate(() => localStorage.getItem('authToken'));
expect(token).not.toBeNull();
});
Тут добре видно межу: process.env працює в Node-частині, а localStorage — усередині колбека page.evaluate, який Playwright пересилає на виконання в браузер. Переплутати ці два світи — типова помилка новачка.
Кілька API поступово зближуються: сучасні версії Node мають вбудований fetch, який раніше жив лише в браузері. Але фундаментальний поділ — DOM проти файлової системи — лишається: браузер ніколи не отримає прямий доступ до диска користувача, а Node не має сторінки, яку можна намалювати.
Як це запускати: node file.js і REPL
Щоб виконати JavaScript поза браузером, потрібен встановлений Node.js. Далі є два основні режими.
Запуск файлу. Пишеш код у файл (скажімо, check.js) і передаєш його Node:
node check.js
Node прочитає файл, віддасть його V8 на виконання й завершиться, коли код відпрацює (і не лишиться незавершених асинхронних задач). Це те, що під капотом роблять тестові ранери: npx playwright test — це, по суті, Node, який запускає файли з твоїми тестами.
REPL (Read-Eval-Print Loop — «читай, обчисли, надрукуй, повтори») — інтерактивна консоль. Запускаєш node без аргументів і отримуєш запрошення, куди можна вводити вирази й одразу бачити результат:
$ node
> 2 + 2
4
> [3, 1, 2].sort()
[ 1, 2, 3 ]
> .exit
REPL зручний, щоб швидко перевірити поведінку методу, не створюючи файл: «а sort мутує масив чи повертає новий?», «що поверне parseInt('08')?». Аналог у браузері — консоль DevTools, тільки там навколо тебе браузерне середовище з document, а в Node REPL — серверне з process і fs. Виходять із REPL командою .exit або двічі натиснувши Ctrl+C.
Важливий нюанс, який спливе далі: рушій виконує JavaScript, а не TypeScript. Файл .ts напряму node file.ts історично не запускав — його спершу треба було скомпілювати в JS або скористатися завантажувачем на кшталт tsx чи ts-node. Свіжі версії Node (24 LTS) вже вміють запускати .ts самі, просто прибираючи анотації типів, але без перевірки цих типів — її, як і раніше, робить окремий інструмент. Тестові ранери зазвичай беруть трансформацію на себе, тому в проєкті ти пишеш .ts, а Node під капотом отримує вже JavaScript. Механіка цього — у главі /js-ts/moduli-ta-npm-import-export-packagejson і в главі про tsconfig.
Типові помилки
Виглядає як «мова зламалась», а насправді код виконується не в тому середовищі. document is not defined у тесті — не баг Node і не помилка синтаксису. Це сигнал, що ти звернувся до браузерного API з Node-частини коду. Рішення — виконати цей код у контексті сторінки (page.evaluate), а не в тілі тесту.
Виглядає як «Node.js — це мова», а насправді це середовище виконання. Node.js не мова програмування й не «інша мова, схожа на JS». Це рантайм, який виконує звичайний JavaScript поза браузером. На співбесіді відповідь «Node — це мова» одразу видає нерозуміння базової картини.
Виглядає як «JavaScript і ECMAScript — одне й те саме слово», а насправді це специфікація і мова. ECMAScript — стандарт (ECMA-262), JavaScript — мова, що його реалізує. Плутанина нешкідлива в побуті, але питання «що таке ECMAScript?» на співбесіді перевіряє саме це розрізнення.
Виглядає як «браузерний баг лише в одному браузері — це баг мови», а насправді різниця середовища чи рушія. Сама мова в Chrome, Firefox і Safari однакова. Розбіжна поведінка — це майже завжди різні реалізації Web API або особливості рушія, а не «інший JavaScript».
Виглядає як «node запустить мій .ts файл», а насправді рушій виконує лише JavaScript. TypeScript треба спершу перетворити на JS. Якщо node test.ts падає з незрозумілою помилкою — швидше за все, бракує компіляції або завантажувача типу tsx.
Підсумок
- JavaScript — це мова, ECMAScript (ECMA-262) — її специфікація, рушій — програма, що її виконує. Три різні поняття, які легко сплутати словом «JavaScript».
- V8 — один рушій для Chrome і для Node.js. Сам рушій виконує лише мову; усе інше — DOM, файли, мережу — додає навколишнє середовище.
- Браузер і Node.js — два середовища для одного V8. Браузер дає
document,window,localStorage; Node даєfs,process,Buffer. Тому тестовий код бачить файли, але не бачить сторінку. - Код автотесту виконується в Node, а не в браузері. До браузерних API з тесту дістаєшся лише через виконання коду в контексті сторінки.
node file.jsзапускає файл,nodeбез аргументів — REPL для швидких перевірок; рушій виконує JavaScript, а TypeScript спершу компілюється.
Що питають на співбесіді
Ця тема на співбесідах з'являється рідко й радше як розминка — перевірити, чи кандидат розуміє, з чим працює, а не завчив синтаксис напам'ять.
- «Яка різниця між JavaScript і Node.js?» — класичне питання-фільтр. Сильна відповідь: JavaScript — мова, Node.js — середовище виконання, яке дозволяє запускати цю мову поза браузером і додає доступ до файлів, мережі, процесів. Інтерв'юер дивиться, чи не називає кандидат Node мовою.
- «Що таке ECMAScript?» — перевірка на розрізнення специфікації і мови. Достатньо: ECMAScript — це стандарт мови (ECMA-262), який щороку оновлює комітет TC39, а JavaScript — його реалізація.
- «Де виконується код твого Playwright-тесту — у браузері чи ні?» — питання на розуміння архітектури. Правильна відповідь: у Node.js; браузером тест керує окремо, тому в самому тесті немає
document, а браузерний код запускається черезpage.evaluate. - «Чому один і той самий сайт по-різному працює в різних браузерах?» — тут інтерв'юер хоче почути, що мова однакова, а різняться реалізації Web API і рушії, тож причину треба шукати в середовищі, а не в «іншому JavaScript».
На цю тему не варто розповідати довго: показав, що розумієш поділ на мову, рушій і середовище, — і рухаєшся далі до змінних, типів і асинхронності, де питань набагато більше.
Джерела
- MDN: What is JavaScript? — коротке пояснення співвідношення JavaScript і ECMAScript.
- TC39: The ECMAScript specification — офіційний текст стандарту ECMA-262 і процес його розвитку.
- Node.js: Introduction to Node.js — офіційний вступ: що таке Node, його зв'язок із V8 і подієвий цикл.
- V8 documentation — сайт рушія V8: як він виконує JavaScript і де застосовується.
- Node.js REPL documentation — довідка про інтерактивний режим і його команди.
Що ховається за словом «JavaScript»: мова, стандарт чи середовище?
Насправді це три різні шари, які зручно плутати одним словом. Мова — це синтаксис і правила, якими ти пишеш код. Стандарт (ECMAScript, ECMA-262) — текстовий документ, що ці правила описує, але сам нічого не виконує. Рушій (engine) — реальна програма, яка бере твій код і перетворює його на дії процесора. А поверх усього — середовище (браузер чи Node.js), яке додає власні можливості типу DOM або файлової системи. Половина «незрозумілих» помилок новачка в автоматизації — це наслідок того, що людина не бачить межі між цими шарами.
Яка різниця між JavaScript і ECMAScript?
ECMAScript — це стандарт мови (ECMA-262), а JavaScript — конкретна мова, що його реалізує. Стандарт розробляє комітет TC39 і щороку випускає нову редакцію: ES2015 (він же ES6) приніс let, const, класи й стрілочні функції, ES2017 — async/await. Сам стандарт — це текст із правилами, він нічого не запускає. У побуті обидва слова вживають як синоніми, але на співбесіді питання «що таке ECMAScript» перевіряє саме розрізнення: специфікація проти реалізації. Коли кажуть «нова фіча JS», зазвичай мають на увазі свіжу редакцію ECMAScript.
Що таке рушій JavaScript і які основні існують?
Рушій — це програма, яка парсить твій текст коду, компілює його й запускає. У кожного великого вендора свій: V8 у Google (Chrome, Node.js), SpiderMonkey у Mozilla (Firefox), JavaScriptCore в Apple (Safari). Усі вони реалізують один стандарт ECMAScript, тому чистий мовний код на кшталт [1, 2, 3].map(x => x * 2) дає в них однаковий результат. Різниця не в мові, а в тому, які додаткові API навколо рушія додає середовище. Тому «баг тільки в Safari» майже завжди означає різницю рушія чи Web API, а не «інший JavaScript».
Яка різниця між JavaScript і Node.js?
JavaScript — це мова, Node.js — середовище виконання (runtime), яке дозволяє запускати цю мову поза браузером. Node не є ні мовою, ні «діалектом JS»: це програма, що вбудовує рушій V8 і додає до нього доступ до файлової системи, мережі, процесів і змінних оточення. На співбесіді це питання-фільтр — інтерв'юер слухає, чи не назве кандидат Node мовою, бо така відповідь одразу видає нерозуміння картини. Для нас практично важливо, що саме Node запускає тестові ранери: npx playwright test під капотом — це Node, який виконує файли з твоїми тестами.
Що таке V8 і як він повʼязаний одночасно з Chrome і Node.js?
V8 — рушій від Google, написаний на C++, який компілює JavaScript у машинний код під час виконання (JIT, just-in-time), тому працює швидко. Ключове: сам V8 уміє лише виконувати мову — він знає Array і Math, але поняття не має, що таке «клік по кнопці» чи «файл на диску». І Chrome, і Node.js вбудовують один і той самий V8, а відрізняються тим, що додають навколо нього: Chrome дає йому сторінку й DOM, Node — файли й процеси. Тобто один двигун живе у двох різних «будинках».
Чому один і той самий сайт по-різному поводиться в різних браузерах?
Тому що сама мова в Chrome, Firefox і Safari однакова — розходяться реалізації Web API і рушії. Базові конструкції ECMAScript працюють скрізь ідентично, але браузерні можливості (DOM, окремі API) кожен вендор реалізує самостійно, і саме тут виникають розбіжності. Отже, причину «дивної поведінки лише в одному браузері» треба шукати в середовищі, а не в «іншому JavaScript». Для AQA це орієнтир: крос-браузерний баг — це майже завжди різниця Web API чи рушія, а не мови.
Навіщо AQA знати саме JavaScript і TypeScript, а не Python чи Java?
Здебільшого мову диктує екосистема інструментів, а не смак. Провідні тулзи для e2e-тестування вебу — Playwright, Cypress, WebdriverIO, CodeceptJS — написані на JS і TS, і твої тести пишуться тією ж мовою. Узявши іншу мову, ти втрачаєш частину екосистеми: приклади з документації, плагіни, готові відповіді, спільну мову з фронтендерами. Другий аргумент — сам продукт: фронтенд сучасного вебу це JavaScript, тож AQA, який читає код сторінки, заводить точніші баги й швидше добирається до першопричини.
Що TypeScript додає поверх JavaScript і навіщо це в тестах?
TypeScript — це надбудова над JavaScript зі статичною типізацією (static typing), тобто перевіркою типів до запуску. Для тестового коду це страховка: редактор підкаже, що в об'єкта сторінки немає такого методу, ще коли ти друкуєш рядок, а не через годину, коли тест червоніє в CI. Важливо, що TS — не окрема мова, а той самий JavaScript плюс анотації типів. Рушій його напряму не виконує: TypeScript спершу перетворюється на JavaScript.
Де виконується код твого Playwright-тесту — у браузері чи ні?
У Node.js, а не в браузері. Node запускає файл із тестом, а той уже керує окремим браузером через протокол — тобто код тесту й код сторінки живуть у різних середовищах. Саме тому в тілі тесту немає document чи window: ці об'єкти існують лише всередині сторінки. Щоб дотягнутися до браузерних API, тест мусить переслати код на виконання в браузер — у Playwright це page.evaluate. Це класичне питання на розуміння архітектури, а не синтаксису.
Чому в тесті працює require('fs'), але падає звернення до document?
Бо тест виконується в Node, а Node і браузер дають різні набори глобальних об'єктів. Node додає доступ до файлової системи (fs), шляхів (path), процесу (process), але не має жодної сторінки, тому document там просто не існує. Браузер навпаки: у нього є DOM і localStorage, але немає прямого доступу до диска. Помилка document is not defined у тесті — це не баг і не проблема синтаксису, а сигнал, що ти звернувся до браузерного API з Node-частини коду.
Що таке page.evaluate і коли він потрібен?
Це спосіб виконати шматок коду не в Node, а всередині сторінки в браузері. Playwright бере функцію, яку ти передав у page.evaluate, і пересилає її в браузерний контекст, де вже доступні document, window, localStorage. Наприклад, дістати токен із localStorage можна лише так — з тіла тесту він недосяжний. Типова помилка новачка — намагатися звернутися до браузерних API прямо в тесті й не розуміти, чому вони «не визначені».
Як передати в тест базовий URL чи токен, не зашиваючи їх у код?
Через змінні оточення — process.env. Це Node-механізм, тому він доступний прямо в тілі тесту: process.env.BASE_URL чи process.env.TOKEN підставляються ззовні (з CI, файлу .env, команди запуску), і той самий код працює проти різних середовищ без правок. Важливо не плутати межу: process.env живе в Node-частині, а localStorage — усередині сторінки, тож дістати його можна лише через page.evaluate. Ця пара — наочний приклад того, який код у якому світі виконується.
Що таке REPL і чим він корисний для AQA?
REPL (read-eval-print loop, «читай, обчисли, надрукуй, повтори») — інтерактивна консоль Node: запускаєш node без аргументів, вводиш вираз і одразу бачиш результат. Це зручно, щоб швидко перевірити поведінку методу, не створюючи файл: чи sort мутує масив, що поверне parseInt('08'), як поводиться якийсь метод рядка. По суті це серверний аналог консолі DevTools, тільки навколо тебе не браузерне середовище з document, а Node з process і fs. Виходять із нього командою .exit або подвійним Ctrl+C.
Чому node file.ts не завжди запускається і що робити з TypeScript?
Бо рушій виконує JavaScript, а не TypeScript — типи треба спершу прибрати. Історично .ts напряму не запускався: його компілювали в JS або брали завантажувач типу tsx чи ts-node. Свіжі версії Node (24 LTS) вже вміють запускати .ts, просто відкидаючи анотації типів, але без перевірки цих типів — її, як і раніше, робить окремий інструмент. У реальному проєкті тестовий ранер бере трансформацію на себе, тому ти пишеш .ts, а Node під капотом отримує вже чистий JavaScript.
Чим window відрізняється від global і що таке globalThis?
Це глобальний об'єкт середовища, і зветься він по-різному: у браузері — window, у Node — global. Щоб не писати код окремо під кожне середовище, стандарт додав globalThis — єдине ім'я, яке вказує на глобальний об'єкт скрізь. Для AQA це практично: код, який має працювати і в тесті, і всередині сторінки, безпечніше писати через globalThis. Різні імена глобального об'єкта — ще один прояв того, що середовища різні, хоч мова одна.
Що таке host-середовище і чому DOM немає в стандарті ECMAScript?
Host-середовище (host environment) — це програма, яка вбудовує рушій і додає до мови власні можливості: браузер або Node.js. Рушій знає тільки мову — Array, Promise, Math; усе інше дає хост. DOM, window, document описані не в ECMAScript, а в окремих специфікаціях (DOM, Web APIs), які реалізує браузер; так само fs і process — це API Node, а не мови. Тому та сама мова в різних хостах отримує зовсім різний «інвентар», і розуміння цієї межі рятує від плутанини, де який код виконується.
Три ситуації, де плутанина «мова / рушій / середовище» вирішує, зелений тест чи згаяна година: класична помилка document is not defined та її правильне лікування, таблиця рішень «який код у якому світі виконується», і REPL як інструмент швидкої перевірки поведінки JS перед тим, як писати асерт. Скрізь — що робити й чому.
Кейс 1. document is not defined: тест виконується не там, де ти думаєш
Новачок пише перший тест, копіює звичний браузерний код і запускає його. Ось типовий зламаний варіант:
import { test, expect } from '@playwright/test';
test('на дашборді показано токен', async ({ page }) => {
await page.goto('/dashboard');
// document живе в браузері, а цей рядок виконує Node
const token = document.querySelector('#token')?.textContent;
expect(token).toBeTruthy();
});
Запуск падає ще до будь-якого асерту:
ReferenceError: document is not defined
Спокуса — вирішити, що «зламалась мова» або «сторінка не завантажилась». Насправді причина інша: тіло тесту виконує Node, а document існує лише всередині сторінки в браузері. Node керує браузером окремо, тому браузерних об'єктів у нього під рукою немає. Правильний спосіб — переслати саме цей шматок коду в браузер через page.evaluate:
test('на дашборді показано токен', async ({ page }) => {
await page.goto('/dashboard');
// колбек Playwright пересилає в браузер — там document існує
const token = await page.evaluate(() => {
return document.querySelector('#token')?.textContent ?? null;
});
expect(token).toBeTruthy();
});
Що дивитися і чому:
ReferenceError: document is not defined— це не баг Node і не синтаксис. Це сигнал, що ти звернувся до браузерного API з Node-частини коду. Лікування завжди одне: винести звернення в контекст сторінки, а не в тіло тесту.- Асерт лишається в Node.
expect(...)— частина тестового ранера, він працює саме в Node; у браузер їде тільки колбекpage.evaluate, який повертає у Node вже готове значення (рядок абоnull). - У браузер можна передавати лише серіалізовані дані. Функція в
page.evaluateне бачить змінних із тіла тесту напряму — їх передають окремим аргументом. Це прямий наслідок того, що це два різні середовища, а не одна програма.
Кейс 2. Таблиця рішень: який код у якому світі
Коли не впевнений, чому щось «не визначено», спершу визнач середовище. Тіло тесту — це Node; усе, що всередині page.evaluate (чи page.$eval, locator.evaluate), — браузер. Ось найчастіші об'єкти й де вони реально живуть:
| Об'єкт / API | Node (тіло тесту) | Браузер (усередині page.evaluate) |
|---|---|---|
process.env, process.argv | Є | Немає |
fs, path | Є | Немає |
document, елементи сторінки | Немає | Є |
window, localStorage | Немає | Є |
globalThis | Є (як global) | Є (як window) |
fetch | Є (сучасний Node) | Є |
Найпоширеніша пара, де новачок спотикається, — process.env і localStorage в одному тесті. Перше читається в Node, друге — тільки в браузері:
test('токен збережено в localStorage після логіну', async ({ page }) => {
// process.env — Node-механізм, працює прямо тут
await page.goto(process.env.BASE_URL + '/dashboard');
// а localStorage доступний лише всередині сторінки
const token = await page.evaluate(() => localStorage.getItem('authToken'));
expect(token).not.toBeNull();
});
Що дивитися і чому:
- Базовий URL і токени передавай через
process.env, не хардкодь. Це Node-частина, тому змінна доступна прямо в тілі тесту, і той самий код працює проти локалі, стейджа й прода без правок. globalThis— безпечний спосіб не думати про ім'я глобального об'єкта. У Node він указує наglobal, у браузері — наwindow; якщо код має жити в обох світах, звертайся черезglobalThis.- Помилка «X is not defined» майже завжди про середовище. Перш ніж чіпати код застосунку, спитай себе: цей рядок — Node чи браузер? Половина «магічних» падінь зникає на цьому питанні.
Кейс 3. REPL і node file.js: перевір поведінку до того, як писати асерт
Перед тим як зафіксувати асерт, буває корисно швидко перевірити, як метод поводиться насправді — а не як здається. Класична пастка: Array.prototype.sort мутує оригінальний масив, тому наївний тест може «зіпсувати» дані, які перевіряє далі. Замість гадати — відкрий REPL (node без аргументів) і подивись:
$ node
> const a = [3, 1, 2];
> a.sort();
[ 1, 2, 3 ]
> a
[ 1, 2, 3 ] // оригінал змінився: sort мутує масив на місці
> parseInt('08');
8
> .exit
За п'ять секунд ти дізнався дві речі, які легко переплутати з пам'яті: sort повертає той самий (уже відсортований) масив і мутує оригінал, а parseInt('08') дає 8, а не падає й не читає це як вісімкову систему. Тепер асерт можна писати свідомо. Якщо перевірка складніша за один рядок, той самий код кладуть у файл і запускають:
node check.js
Node прочитає файл, віддасть V8 на виконання й завершиться, коли код відпрацює. Це рівно те, що під капотом роблять тестові ранери: npx playwright test — по суті Node, який запускає файли з тестами.
Що дивитися і чому:
- REPL — це Node-середовище, не браузер. Тут є
processі можнаrequire('fs'), але немаєdocument: перевіряти браузерну поведінку в ньому не вийде — для цього є консоль DevTools. - Мутація методів — часте джерело прихованих багів у тестах.
sort,reverse,spliceзмінюють масив на місці; якщо готуєш дані для кількох перевірок, спершу скопіюй масив, інакше другий асерт побачить уже зіпсований стан. - REPL не замінює тест, а економить ітерацію. Він відповідає на питання «як це працює?» без збірки, запуску браузера й очікування CI — і саме тому пришвидшує написання надійних асертів.
Мова, специфікація і рушій
- Розрізняю три шари за словом «JavaScript»: мову, специфікацію (ECMAScript, ECMA-262) і рушій, що виконує код.
- Можу пояснити, чому ECMAScript — це стандарт (ECMA-262), а JavaScript — його реалізація, і що редакції щороку випускає комітет TC39: ES2015/ES6 (
let,const, класи, стрілки), ES2017 (async/await). - Знаю три основні рушії й хто їх робить: V8 (Google), SpiderMonkey (Mozilla), JavaScriptCore (Apple).
- Розумію, що рушій виконує лише мову, а всі «зовнішні» можливості (DOM, файли) додає середовище навколо нього.
Браузер і Node.js — два середовища
- Можу пояснити, що V8 — один і той самий рушій у Chrome і в Node.js, а відрізняється те, що навколо нього.
- Знаю, що браузер дає
document,window, DOM,localStorage, а Node —fs,path,process,Buffer. - Розумію, чому DOM немає в стандарті ECMAScript: це окремі Web API, які реалізує браузер.
- Можу пояснити різницю глобального об'єкта:
windowу браузері,globalу Node,globalThisскрізь. - Знаю, що Node — це середовище виконання, а не мова, і чому відповідь «Node — це мова» провальна на співбесіді.
Node.js зблизька
- Знаю, що Node створив Ryan Dahl (2009) і що він поєднує V8 з бібліотекою libuv (асинхронний I/O, event loop).
- Розумію, що частина API зближується (сучасний Node має вбудований
fetch), але поділ «DOM проти файлової системи» лишається. - Можу пояснити, що
require/CommonJS таimport/ES-модулі співіснують у Node, тоді як браузер працює лише з ES-модулями.
Запуск коду і TypeScript
- Знаю різницю між
node file.js(запуск файлу) іnodeбез аргументів (REPL). - Можу пояснити, навіщо REPL AQA: швидко перевірити поведінку методу, не створюючи файл.
- Розумію, що рушій виконує JS, а не TS:
.tsспершу компілюється в JS або йде через завантажувач (tsx,ts-node). - Знаю, що свіжий Node (24 LTS) прибирає типи сам, але не перевіряє їх — це робить окремий інструмент.
Межа Node/браузер у тестах
- Розумію, що код Playwright-тесту виконується в Node, а браузером він керує окремо через протокол.
- Можу пояснити, чому
document is not definedу тесті — це не баг, а звернення до браузерного API з Node-частини. - Знаю, що до браузерних API (
document,localStorage) з тесту дістаюся лише черезpage.evaluate. - Можу пояснити, чому
process.envпрацює прямо в тілі тесту, аlocalStorage— тільки всерединіpage.evaluate.
Що таке ECMAScript?
Питання
Які три шари ховаються за словом «JavaScript»?