Содержание
- Когда использовать песочницу
- Подготовка
- Запуск страницы расширения
- Запуск виджета
- Как увидеть изменения в песочнице
- Фикстуры и контекст
- Уровни автоматизированных тестов
- Unit-тесты в jsdom
- Браузерный тест worker-расширения
- E2E-тест с Playwright
- Возможные проблемы
Песочница @retailcrm/embed-ui-v1-sandbox позволяет запускать страницы и виджеты JS-модуля без установки в RetailCRM. Она воспроизводит необходимую часть интерфейса карточки заказа, передаёт расширению контекст и имитирует Host API.
Песочница предназначена для JS-модулей Embed UI v1, которые запускаются в worker с параметром "runner": "worker". Расширения с устаревшим iframe-runner не поддерживаются.
Когда использовать песочницу
Песочница подходит для трёх задач:
- ручной проверки страницы или виджета во время разработки;
- браузерных тестов worker-расширения с управляемыми ответами Host API;
- сквозных тестов доставки расширения, стилей и запросов к серверу модуля.
Проверка в песочнице не заменяет финальную проверку установленного модуля в RetailCRM. В CRM дополнительно проверьте права, доступность пунктов меню, совместимость с данными аккаунта и взаимодействие с остальным интерфейсом.
Подготовка
Если frontend-проект расширения ещё не подготовлен, сначала создайте его через CLI @retailcrm/embed-ui. Команда init и доступные параметры описаны в разделе «Создание frontend-проекта через CLI».
Добавьте пакет песочницы в зависимости для разработки:
yarn add -D @retailcrm/embed-ui-v1-sandbox
Запустите сервер расширения. Он должен отдавать descriptor или entrypoint по HTTP. Для стандартного сервера расширений используется адрес:
%extension-url%/extension/%extension-id%
Здесь %extension-id% — UUID расширения, а не код страницы и не цель виджета. Одно расширение может одновременно содержать несколько страниц и виджетов.
UUID расширения хранится в extensionrc.json. Например:
{
"code": "returnsModule",
"name": "Возвраты",
"uuid": "79aa7a7a-3b66-4e85-b623-f7c1fef97bc7",
"version": "0.1.0",
"entrypoint": "script",
"runner": "worker",
"stylesheet": true,
"pages": [
{
"code": "returns",
"menu": "activity_main_menu",
"menuItemOrdering": 15,
"menuItemTitle": {
"ru": "Возвраты",
"en": "Returns",
"es": "Devoluciones"
},
"pageHelpLink": null
}
]
}
В этом примере полный URL расширения заканчивается на /extension/79aa7a7a-3b66-4e85-b623-f7c1fef97bc7, а код страницы для режима «Страница» — returns.
Затем в отдельном терминале запустите песочницу:
npx @retailcrm/embed-ui-v1-sandbox serve
По умолчанию сервер принимает подключения на порте 4173. Если песочница запускается в контейнере, явно укажите доступные с хост-машины адрес и порт:
npx @retailcrm/embed-ui-v1-sandbox serve --host 127.0.0.1 --port 4173
Откройте адрес из вывода команды. Код расширения не входит в приложение песочницы: сервер расширения должен работать параллельно и быть доступен из браузера.
Запуск страницы расширения
- Нажмите кнопку
< />в нижней части тёмной боковой панели. - В поле «Манифест / URL расширения» укажите полный адрес расширения вида
%extension-url%/extension/%extension-id%. - Выберите режим «Страница».
- Выберите фикстуру с исходным состоянием CRM.
- Введите код страницы — значение
codeиз регистрации страницы расширения. - Нажмите «Применить».
Например, если descriptor содержит страницу returns, укажите returns в поле «Код страницы». UUID расширения в это поле вводить не нужно.
После применения настроек песочница загружает worker, создаёт Host API и показывает страницу в рабочей области.
Тот же запуск можно воспроизвести прямой ссылкой:
%sandbox-url%/?manifestUrl=%extension-url%/extension/%extension-id%&mode=page&pageCode=returns&fixture=order-basic
Замените %sandbox-url%, %extension-url% и %extension-id% фактическими адресами песочницы, сервера расширения и UUID из extensionrc.json.
Как понять, что расширение загрузилось
Если в рабочей области появилась ожидаемая страница, значит песочница получила descriptor расширения, загрузила entrypoint, запустила worker и смонтировала Vue-компонент.
В проекте, созданном через CLI @retailcrm/embed-ui, страница «Настройки расширения» — интерактивный пример. Это не настройка самой песочницы и не обязательная часть будущего модуля. Поля показывают работу компонентов Embed UI, а кнопка «Сохранить» меняет только локальное состояние примера. Данные не отправляются на сервер и сбрасываются после обновления страницы.
После нажатия кнопки «Сохранить» появляется сообщение об успешном локальном сохранении:
Если проект создан командой init ./src, исходник примера находится в src/pages/SettingsPage.vue. Регистрация страницы находится в src/endpoint/endpoint.worker.ts, а её page code и остальные параметры — в extensionrc.json. Если при инициализации выбран другой frontend-каталог, замените src на путь к этому каталогу.
Запуск виджета
- В поле «Манифест / URL расширения» укажите адрес расширения.
- Выберите режим «Виджеты».
- Отметьте одну или несколько целей в поле «Места встраивания виджетов».
- Выберите фикстуру.
- Нажмите «Применить».
Цель — место карточки заказа, в которое RetailCRM монтирует виджет. Например, order/card:common.after располагается после блока с основными данными заказа. Если выбрать несколько целей, песочница создаст отдельный экземпляр виджета для каждой из них.
Прямая ссылка для запуска виджета:
%sandbox-url%/?manifestUrl=%extension-url%/extension/%extension-id%&mode=widget&targets=order/card:common.after&fixture=order-basic
Как выбирать места встраивания
Поле «Места встраивания виджетов» содержит все targets, поддерживаемые песочницей, а не только targets подключённого расширения. Содержимое появится только для target, который указан в extensionrc.json и зарегистрирован через defineWidgetRunner в endpoint worker. Пустой блок для незарегистрированного target не означает сбой песочницы.
В стартовом проекте виджет зарегистрирован для order/card:common.after. Выберите эту цель, чтобы проверить сгенерированный пример.
Что показывает стартовый виджет
Стартовый виджет — интерактивный пример. Кнопка, ссылка, боковая панель и модальное окно показывают возможности компонентов Embed UI. Песочница передаёт виджету текущий target, а клиент, заметка и остальные значения заданы в исходном коде примера.
Кнопка «Сохранить черновик» сохраняет введённые значения только в памяти текущего экземпляра виджета. После обновления страницы они сбрасываются. Кнопки в модальном окне закрывают его, но не изменяют заказ и не отправляют данные на сервер. Реальное сохранение реализуйте в коде расширения, например через backend модуля и host.httpCall().
Для проекта, созданного командой init ./src, исходник виджета находится в src/widgets/OrderCommonAfterWidget.vue. Если выбран другой frontend-каталог, замените src на его путь.
Как увидеть изменения в песочнице
Для первого запуска проекта, созданного через CLI, используйте:
npm run dev
Команда один раз собирает расширение, затем запускает сервер расширения и песочницу. Она не отслеживает изменения исходников и не выполняет автоматическую пересборку.
После изменения кода:
- остановите
npm run devсочетаниемCtrl+C; - снова выполните
npm run dev; - обновите страницу песочницы или снова откройте панель и нажмите «Применить».
Чтобы управлять процессами отдельно, сначала соберите и запустите сервер расширения:
npm run build
npm run extension:serve
В другом терминале запустите песочницу:
npm run sandbox:serve
После изменения исходников повторно выполните npm run build, перезапустите npm run extension:serve и обновите страницу песочницы либо повторно примените настройки запуска. Сервер npm run sandbox:serve перезапускать не требуется.
Если меняется только содержимое существующей страницы или виджета, достаточно изменить соответствующий Vue-компонент. При добавлении или переименовании страницы, page code либо target обновите и регистрацию в endpoint.worker.ts, и параметры в extensionrc.json.
Фикстуры и контекст
Фикстура задаёт исходное состояние CRM: заказ, текущего пользователя, настройки карточки и справочники. В пакет входят три фикстуры заказа:
order-basic— обычный заказ без доставки;order-with-delivery— заказ с адресом и данными доставки;order-readonly-error— отменённый заказ с ограничениями на редактирование.
В редакторе «JSON контекста текущего запуска» можно временно изменить контекст. Это удобно для проверки пустых значений, другого статуса заказа или ограниченных прав без изменения исходного кода расширения.
Например, чтобы изменить номер заказа:
{
"order/card": {
"number": "999C"
}
}
Нажмите «Применить контекст». Песочница проверит JSON, обновит контекст и перезапустит расширение. После успешного применения появится уведомление «Контекст применён. Расширение перезапущено».
Сейчас поддерживаются следующие цели карточки заказа:
order/card:common.before
order/card:common.after
order/card:customer.before
order/card:customer.after
order/card:customer.email
order/card:customer.phone
order/card:list.before
order/card:list.after
order/card:store.before
order/card:dimensions.before
order/card:delivery.before
order/card:delivery.after
order/card:delivery.address
order/card:payment.before
order/card:comment.manager.before
Уровни автоматизированных тестов
Выбирайте самый простой уровень, который воспроизводит проверяемое поведение.
| Уровень | Для чего подходит | Что не проверяет |
|---|---|---|
| Unit + jsdom | Форматирование, преобразование данных, построение запросов, изолированные компоненты | Worker, настоящий браузер и delivery расширения |
| Vitest Browser | Запуск worker в Chromium, страницы и виджеты, контекст, основные действия пользователя | Полную загрузку расширения с отдельного сервера и реальное проксирование запросов |
| Playwright E2E | Интерфейс песочницы, /extension/:id, подключение стилей, проксирование Host API и критический пользовательский сценарий |
Не предназначен для полного перебора мелких вариантов логики |
Обычную бизнес-логику лучше покрывать unit-тестами. В браузерных тестах проверяйте основные сценарии страницы и виджета с предсказуемыми ответами Host API. Для E2E оставьте один-два критических сценария, в которых важна совместная работа всех частей.
Unit-тесты в jsdom
Unit-тестам песочница обычно не нужна. Запускайте их в окружении jsdom и проверяйте функции форматирования, подготовку запросов, преобразование данных и изолированные компоненты. Такие тесты выполняются быстрее браузерных и E2E-сценариев, поэтому переносите в них проверки, которым не нужен worker или Host API.
Минимальная настройка Vitest:
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
environment: 'jsdom',
include: ['src/**/*.test.ts'],
},
})
Браузерный тест worker-расширения
Браузерный тест запускает настоящий worker в Chromium, но не требует отдельного сервера расширения. Входной файл worker загружает Vite, а ответы host.httpCall при необходимости задаются в тесте.
Настройте Vitest Browser Mode с Playwright provider и добавьте шаблон *.browser.test.ts в test.include. Если проект создан через embed-ui, готовая конфигурация находится в vitest.config.browser.ts.
import type { SandboxWorkerRuntime } from '@retailcrm/embed-ui-v1-sandbox/automation/browser'
import { afterEach, expect, test } from 'vitest'
import { screen } from '@testing-library/dom'
import {
createExtensionSourceWorker,
createSandboxWorkerRuntime,
} from '@retailcrm/embed-ui-v1-sandbox/automation/browser'
let runtime: SandboxWorkerRuntime | null = null
afterEach(async () => {
await runtime?.teardown()
runtime = null
document.body.innerHTML = ''
})
test('opens extension page', async () => {
const sourceWorker = createExtensionSourceWorker(
new URL('/src/endpoint.worker.ts', window.location.href)
)
runtime = await createSandboxWorkerRuntime({
descriptorUuid: 'my-extension',
fixture: 'order-basic',
ready: sourceWorker.ready,
worker: sourceWorker.worker,
})
await runtime.runPage('settings')
expect(
await screen.findByRole('heading', { name: 'Настройки расширения' })
).toBeInstanceOf(HTMLElement)
})
Для виджета вызовите runtime.runWidget('order/card:common.after'). После каждого теста обязательно вызывайте runtime.teardown(): метод освобождает worker, RPC и смонтированные компоненты.
Стили для такого теста подключайте только тогда, когда они влияют на поведение, например на видимость или возможность нажать элемент. Загрузку CSS проверяйте в E2E-тесте.
Ищите элементы по роли, подписи поля и видимому тексту. Такие проверки отражают пользовательский сценарий и меньше зависят от внутренней разметки компонента.
E2E-тест с Playwright
E2E-тест запускает приложение песочницы и настоящий сервер расширения. Он проверяет endpoint /extension/:id, загрузку файла стилей и передачу host.httpCall на сервер модуля.
Адреса удобно хранить в локальном .env.sandbox:
SANDBOX_BASE_URL=http://127.0.0.1:4173
SANDBOX_EXTENSION_URL=http://127.0.0.1:3000/extension/
Не добавляйте .env.sandbox в репозиторий. Вместо него оставьте .env.sandbox.dist без секретов и персональных адресов.
Загрузите переменные в конфигурации Playwright и передайте адрес песочницы в use.baseURL:
import path from 'node:path'
import dotenv from 'dotenv'
import { defineConfig } from '@playwright/test'
dotenv.config({ path: path.resolve(process.cwd(), '.env.sandbox') })
export default defineConfig({
use: {
baseURL: process.env.SANDBOX_BASE_URL,
},
})
Перед тестом запустите песочницу и сервер расширения вручную или добавьте их в webServer конфигурации Playwright. В проектах, созданных через embed-ui, эта настройка уже добавлена в vitest.config.playwright.ts.
import { expect, test } from '@playwright/test'
import {
createSandboxPagePath,
readSandboxSnapshot,
} from '@retailcrm/embed-ui-v1-sandbox/automation/playwright'
const extensionBaseUrl = process.env.SANDBOX_EXTENSION_URL
if (!extensionBaseUrl) {
throw new Error('SANDBOX_EXTENSION_URL is required')
}
const extensionUrl = new URL(
'79aa7a7a-3b66-4e85-b623-f7c1fef97bc7',
extensionBaseUrl
).href
test('opens and saves return', async ({ page }) => {
await page.goto(createSandboxPagePath({
extensionUrl,
fixture: 'order-basic',
manifestUrl: extensionUrl,
pageCode: 'returns',
}))
await expect(
page.getByRole('heading', { name: 'Список возвратов' })
).toBeVisible()
await page.getByRole('button', { name: 'Создать возврат' }).click()
const snapshot = await readSandboxSnapshot(page)
expect(snapshot.host.http.some(call => call.action === '/returns')).toBe(true)
expect(snapshot.host.http.every(call => call.response.status === 200)).toBe(true)
})
Для виджета используйте createSandboxWidgetPath:
await page.goto(createSandboxWidgetPath({
extensionUrl,
fixture: 'order-basic',
manifestUrl: extensionUrl,
targets: ['order/card:common.after'],
}))
readSandboxSnapshot(page) возвращает зафиксированные обращения к Host API и текущее состояние контекста. Проверяйте через snapshot ожидаемое действие и данные запроса, а через интерфейс — результат, который видит пользователь.
Возможные проблемы
Расширение не загружается
Проверьте, что сервер расширения запущен, доступен из браузера и разрешает CORS-запросы от адреса песочницы. В поле «Манифест / URL расширения» должен быть полный HTTP- или HTTPS-адрес endpoint расширения.
Шаблон с %sandbox-url%, %extension-url% и %extension-id% нельзя вставлять в поле без замены значений: поле принимает абсолютный URL с протоколом HTTP или HTTPS.
Страница не найдена
Убедитесь, что значение поля «Код страницы» совпадает с code в descriptor расширения. UUID расширения и код страницы — разные значения.
Сообщение об отсутствующей странице содержит доступные коды страниц. Например, settings — код страницы конкретного стартового расширения, а не обязательное название для любой первой страницы.
Виджет не появился
Проверьте, что выбранная цель зарегистрирована расширением, указана в extensionrc.json и входит в список поддерживаемых целей песочницы.
Изменения не появились
После изменения исходников заново соберите расширение и перезапустите его сервер. Проверьте, что сервер отдаёт актуальные файлы из dist, затем обновите страницу песочницы или повторно примените параметры запуска.