Привет! Я инженер по контролю качества продукта Amplicode в компании Haulmont. Одним из направлений деятельности нашей компании является разработка плагинов для IntelliJ IDEA и расширений для VSCode. Передо мной встала задача протестировать расширение Amplicode Frontend для VS Code. Задача оказалась не самой тривиальной и в процессе мы столкнулись с немалым количеством проблем и нюансов, о которых я и хочу рассказать в этой статье.
Как мы тестировали раньше?
Раньше мы тестировали расширение для VS Code вручную, но функциональность увеличивается, и проходить каждый раз одни и те же проверки становится не продуктивно. Поэтому было решено автоматизировать этот процесс.
Есть два популярных фреймворка для тестирования расширений VS Code: Extester и WebdriverIO. Extester построен на базе Selenium WebDriver и практически “наследует” его принципы, добавляя API, которые упрощают работу с VS Code. Кроме того, Extester создавался исключительно для тестирования расширений VS Code. WebdriverIO реализует два протокола: WebDriver Protocol и Chrome DevTools Protocol, и в целом является более универсальным иснтрументом для автоматизации тестирования. Мы попробовали оба фреймворка, однако не будем проводить подробное их сравнение в этой статье. Напишем отдельную, если тема вызовет интерес у читателя. Скажем лишь, что знакомство с WebdriverIO проходило более гладко и не без удовольствия благодаря хорошей документации и активному комьюнити, чем Extester похвастаться не может.
Далее я опишу некоторые, на мой взгляд, важные проблемы и нюансы при использовании WebdriverIO.
Фреймы
VS Code был разработан на платформе Electron, а для создания инструментов расширения было решено использовать WebView. Эта технология создает фреймы в DOM, что является важным аспектом, поскольку мы используем WebView для отображения контента, но для поиска элементов по-прежнему полагаемся на x-path локаторы.
Однако, когда я открываю окно WebView и пытаюсь найти нужный элемент, он оказывается в другом фрейме, и я не могу получить к нему доступ. В такой ситуации мне приходится переключаться между фреймами. Но в VSCode вместо одного фрейма создаются два. Это означает, что после открытия WebView мне нужно дважды переключаться между фреймами, чтобы получить доступ к элементам внутри. Вот как это выглядит на примере:
const addAuth = await browser.$(this.locators.addAuth);
await addAuth.isDisplayed();
await addAuth.click();
const frame1 = await browser.findElement(
"css selector",
"iframe.webview.ready"
);
await browser.switchToFrame(frame1);
const frame2 = await browser.findElement(
"css selector",
"iframe#active-frame"
);
await browser.switchToFrame(frame2);
const configure = await browser.$(this.locators.configureButton);
await configure.isDisplayed();
await configure.click();
await browser.switchToParentFrame();
await browser.switchToParentFrame();
const basicAuth = await browser.$(this.locators.basicFile);
await basicAuth.waitForDisplayed({ timeout: 5000 });
await expect(basicAuth).toBeDisplayed();
Чтобы переключиться между фреймами, нужно найти именно элемент, а не только его ID. На просторах Google можно найти информацию, о том, что переключение возможно и просто по ID, но это не так. Метод switchToFrame должен получить элемент, который мы находим с помощью метода findElement, в который необходимо передать тип (CSS-селектор) и непосредственно сам селектор. Таким образом, мы сможем переключиться на нужный нам фрейм.
Важно помнить, что после того, как мы выполним все необходимые действия в окне WebView, нужно вернуться обратно. Для этого необходимо выполнить browser.switchToParentFrame(), и в моем случае я делаю это дважды (помним, что при открытии WebView создается 2 фрейма, и переключаюсь я 2 раза).
Расширение и проект
Так как мы будем тестировать расширение, необходимо, чтобы оно было установлено при запуске инстанса VS Code, когда мы прогоняем тесты. Расширения в VSCode устанавливаются как VSIX-архивы, но фреймворк не сможет прочитать такой архив. Я взял расширение, изменил его формат на ZIP и распаковал содержимое. Чтобы установить расширение в тестах, нужно указать путь в конфигурации (wdio.conf.ts) следующим образом:
const __dirname = url.fileURLToPath(new URL('.', import.meta.url));
const __dirnameExtension = path.join(__dirname, '.extension', 'extension');
extensionPath: __dirnameExtension,
Также необходимо создать папку с проектом внутри директории с тестами, который будет открываться в тестах, и указать путь к нему:
const __dirnameWorkspace = path.join(__dirname, 'test', 'test-app');
workspacePath: __dirnameWorkspace,
Хочу обратить внимание на то, что запускается настоящая версия VSCode с вашим расширением, поэтому если в проекте (на котором будут крутиться тесты) будут происходить изменения, их нужно откатывать, чтобы в следующий раз тесты работали корректно. И забегая вперед — это момент для автоматизации.
Reporters (Отчеты)
Важной частью автоматизированных тестов является reporter, который позволяет отлаживать код, отслеживать ошибки и красиво структурировать пройденные и не пройденные тесты. Можно записывать видео, делать скриншоты, подключать Allure со всеми графиками, но я остановился на HTML, это мне ближе и больше нравится (в Playwright есть подобный репортер, который мне также нравится). Это не проблема, а важная часть тестирования. Репортер указывается также в конфигурации:
['html-nice', {
outputDir: './reports/html-nice/',
filename: 'report.html',
override: true,
reportTitle: 'Test Report',
}]],
Также в package.json указываем скрипт:
"report": "open reports/html-nice/report-0-1.html"
Если мы хотим запустить отчет, выполняем команду npm run report. Но каждый раз вручную запускать отчет мы, конечно же, не будем. Мы допишем запуск отчета в скрипт запуска тестов, чтобы отчет открывался автоматически после прохождения тестов.
Вспомогательные скрипты и команды
Первый момент касается распаковки нашего расширения. Не будете же вы каждый раз вручную менять формат расширения и распаковывать его? Это риторический вопрос. Я написал скрипт, который будет искать файл с расширением .vsix, менять его формат на .zip и распаковывать в корень проекта.
import fs from'fs';
import * as path from "node:path";
import * as url from "node:url";
import unzipper from 'unzipper';
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
const vsixFilePath = path.join(__dirname, '.extension.vsix');
if (!fs.existsSync(vsixFilePath)) {
console.error('Файл .vsix не найден, продолжаем выполнение тестов');
process.exit(0);
}
const zipFilePath = vsixFilePath.replace('.vsix', '.zip');
fs.renameSync(vsixFilePath, zipFilePath);
const extractPath = path.join(__dirname, '.extension');
if (!fs.existsSync(extractPath)) {
fs.mkdirSync(extractPath);
}
fs.createReadStream(zipFilePath)
.pipe(unzipper.Extract({ path: extractPath }))
.on('close', () => {
console.log('Файл успешно распакован в директорию .extension');
})
.on('error', (err) => {
console.error('Ошибка при распаковке файла:', err);
});
Второй момент автоматизации — это запуск нашего репортера после завершения тестов. Каждый раз делать это вручную неудобно, и зачем, если это легко решается? Здесь нужно правильно указать команду в наших скриптах.
"report": "open reports/html-nice/report-0-1.html"
Третий момент автоматизации — это очистка созданных данных. Так как наши тесты работают на настоящем проекте, если мы создали данные, мы должны их очистить после завершения тестов. Это тоже решается командой в скриптах, но здесь есть привязка к Git.
"cleanup": "git checkout . && git clean -fd"
Скрипты, прописанные в package.json:
"scripts": {
"unzip": "node unzipper.js",
"test": "npm-run-all --continue-on-error unzip test:run report cleanup",
"test:run": "wdio run ./wdio.conf.ts",
"report": "open reports/html-nice/report-0-1.html",
"cleanup": "git checkout . && git clean -fd"
}
Обращаю внимание на то, что нам понадобятся сторонние библиотеки для выполнения распаковки, работы с файловой системой и запуска нескольких команд сразу:
unzip — для распаковки
npm-run-all — для запуска команд
Заключение
Я решил проблему автоматизации тестирования расширения в VSCode таким способом. В первую очередь я решил написать эту статью, потому что очень мало информации о том, как можно реализовать такой проект. Заострил свое внимание на важных и сложных проблемах, с которыми я столкнулся. Возможно, эта статья поможет тем, кто столкнется с такой же проблемой, как и я.
P.S. Стоит заметить, что хоть и информации по этой теме очень мало, есть дискорд разработчиков. Там можно задавать вопросы, и они отвечают (именно они помогли мне решить проблему с установкой расширения в инстанс). Также есть git репозиторий, все ссылки есть на сайте.