vyvchy
    Теми розділу

    08 · JavaScript/TypeScript для AQA

    JavaScript і Node.js: мова, рушії, середовища виконання

    Зміст

    Ти сідаєш писати перший автотест на Playwright, копіюєш приклад із документації, запускаєш — і бачиш ReferenceError: document is not defined. Хоча в браузері document є завжди. Або навпаки: у тесті працює require('fs'), а той самий рядок у консолі браузера падає. Це не баг і не магія — це наслідок того, що «JavaScript» насправді складається з трьох різних шарів: мови, рушія (engine) і середовища виконання (runtime). Плутанина між ними — джерело половини незрозумілих помилок у новачка в автоматизації.

    Ця глава дає карту цих трьох шарів. Вона легка й на співбесідах важить мало, але без неї решта розділу висить у повітрі: ти не зрозумієш, чому тестовий код бачить файлову систему, але не бачить кнопку на сторінці, і де саме виконується той код, який ти пишеш. Розберемося, що таке ECMAScript, чим Node.js відрізняється від браузера, і як усе це запускати руками.

    Мова автоматизації зазвичай не питання смаку — її диктує екосистема інструментів. Провідні тулзи для 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) — це вже програма, яка бере твій текст коду й виконує його: парсить, компілює в машинний код і запускає. Різні виробники мають свої рушії:

    РушійХто розробляєДе працює
    V8GoogleChrome, Chromium-браузери, Node.js
    SpiderMonkeyMozillaFirefox
    JavaScriptCoreAppleSafari, WebKit

    Усі вони реалізують той самий стандарт ECMAScript, тому базовий код [1, 2, 3].map(x => x * 2) поводиться в них однаково. Розбіжності починаються не в мові, а в тому, які додаткові можливості дає навколишнє середовище. Саме тому «браузерний баг лише в Safari» — це майже завжди різниця середовища або рушія, а не самої мови.

    Специфікація мови
    ECMAScript (ECMA-262):
    синтаксис, типи, Array, Promise

    Рушій
    V8 / SpiderMonkey / JavaScriptCore
    парсить і компілює у машинний код

    Браузер
    window, document, DOM,
    localStorage

    Node.js
    fs, path, process,
    Buffer

    Специфікація мови
    ECMAScript (ECMA-262):
    синтаксис, типи, Array, Promise

    Рушій
    V8 / SpiderMonkey / JavaScriptCore
    парсить і компілює у машинний код

    Браузер
    window, document, DOM,
    localStorage

    Node.js
    fs, path, process,
    Buffer

    Читати діаграму знизу вгору: середовище дає 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
    Глобальний об'єктwindowglobal (обидва мають 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».

    На цю тему не варто розповідати довго: показав, що розумієш поділ на мову, рушій і середовище, — і рухаєшся далі до змінних, типів і асинхронності, де питань набагато більше.

    Джерела