diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..65189ab --- /dev/null +++ b/.env.example @@ -0,0 +1,11 @@ +#################### +### EXAMPLE FILE ### +#################### + +# Do NOT edit this file unless adding new env variables +# Instead, copy it, remove the .example extension and provide your custom values +# For git password please use your generated git token, not a clear text password + + +VSCODE_EXECUTABLE_PATH='/usr/share/code/code' +VSIX_FILE_PATH='kai-vscode-plugin-0.0.3.vsix' diff --git a/.github/workflows/prettier.yaml b/.github/workflows/prettier.yaml new file mode 100644 index 0000000..75de6da --- /dev/null +++ b/.github/workflows/prettier.yaml @@ -0,0 +1,11 @@ +name: Code formatting +on: pull_request + +jobs: + build: + name: Prettier + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - run: npm install . + - run: npm run check diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..3481057 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,6 @@ +{ + "singleQuote": true, + "semi": true, + "trailingComma": "es5", + "tabWidth": 2 +} diff --git a/README.md b/README.md index 59a9732..70146b8 100644 --- a/README.md +++ b/README.md @@ -1 +1,69 @@ -# kai-ci \ No newline at end of file +# VSCode Automation with Playwright + +This repository contains automated tests using Playwright to launch Visual Studio Code (VSCode) and install a specified extension from a VSIX file. + +## Table of Contents + +- [Features](#features) +- [Prerequisites](#prerequisites) +- [Installation](#installation) +- [Usage](#usage) +- [Test Structure](#test-structure) +- [Running Tests](#running-tests) +- [Contributing](#contributing) +- [License](#license) + +## Features + + Launch Visual Studio Code as an Electron application. + Install extensions from VSIX files. + Basic test structure using Playwright's Page Object Model. + +## Prerequisites + +Before you begin, ensure you have the following: + + Node.js (version 14 or later) installed. + Playwright installed. You can install it using npm. + Visual Studio Code installed on your system. + +## Installation + +1. **Clone the Repository** + +`git clone https://github.com/konveyor/kai-ci` +`cd kai-ci` + +2. **Install Dependencies** + +Install the required packages using npm: + +`npm install` + + +## Usage + + Create .env file and copy the content of .env.example into it and provide appropriate values: + + VSCODE_EXECUTABLE_PATH='/usr/share/code/code' + VSIX_FILE_PATH='/home/sshveta/Downloads/kai-vscode-plugin-0.0.3.vsix' + +## Running Tests + +To run the automated tests, use the following command: + +`npx playwright test` + +This command will execute all tests in your repository. To run a specific test file: + +`npx playwright test vscode.test.ts` + +#### Code formatting using Prettier tool + +1. Format code + + `npm run format` + +2. Check formatting + + `npm run check` diff --git a/e2e/pages/vscode.pages.ts b/e2e/pages/vscode.pages.ts new file mode 100644 index 0000000..7153f32 --- /dev/null +++ b/e2e/pages/vscode.pages.ts @@ -0,0 +1,93 @@ +import { _electron as electron, ElectronApplication, Page } from 'playwright'; +import { execSync } from 'child_process'; +import * as fs from 'fs'; + +class LaunchVSCodePage { + private vscodeApp?: ElectronApplication; + private window?: Page; + + private constructor(vscodeApp: ElectronApplication, window: Page) { + this.vscodeApp = vscodeApp; + this.window = window; + } + + public static async launchVSCode( + executablePath: string + ): Promise { + try { + const vsixFilePath = process.env.VSIX_FILE_PATH; + if (vsixFilePath) { + console.log(`Installing extension from VSIX file: ${vsixFilePath}`); + await LaunchVSCodePage.installExtensionFromVSIX(vsixFilePath); + } else { + console.warn( + 'VSIX_FILE_PATH environment variable is not set. Skipping extension installation.' + ); + } + + // Launch VSCode as an Electron app + const vscodeApp = await electron.launch({ + executablePath: executablePath, + }); + + // Get the main window + const window = await vscodeApp.firstWindow(); + + // Return an instance of LaunchVSCodePage + return new LaunchVSCodePage(vscodeApp, window); + } catch (error) { + console.error('Error launching VSCode:', error); + throw error; + } + } + + /** + * Installs an extension from a VSIX file using the VSCode CLI. + * This method is static because it is independent of the instance. + */ + private static async installExtensionFromVSIX( + vsixFilePath: string + ): Promise { + if (!fs.existsSync(vsixFilePath)) { + throw new Error(`VSIX file not found at path: ${vsixFilePath}`); + } + + try { + // Execute command to install VSIX file using VSCode CLI + console.log(`Installing extension from ${vsixFilePath}...`); + execSync(`code --install-extension ${vsixFilePath}`, { + stdio: 'inherit', + }); + console.log('Extension installed successfully.'); + } catch (error) { + console.error('Error installing the VSIX extension:', error); + throw error; + } + } + + /** + * Closes the VSCode instance. + */ + public async closeVSCode(): Promise { + try { + if (this.vscodeApp) { + await this.vscodeApp.close(); + console.log('VSCode closed successfully.'); + } + } catch (error) { + console.error('Error closing VSCode:', error); + } + } + + /** + * Returns the main window for further interactions. + */ + public getWindow(): Page { + if (!this.window) { + throw new Error('VSCode window is not initialized.'); + } + return this.window; + } +} + +export { LaunchVSCodePage }; diff --git a/e2e/tests/vscode.test.ts b/e2e/tests/vscode.test.ts new file mode 100644 index 0000000..777d753 --- /dev/null +++ b/e2e/tests/vscode.test.ts @@ -0,0 +1,34 @@ +import { test, expect } from '@playwright/test'; +import { LaunchVSCodePage } from '../pages/vscode.pages'; + +test.describe('VSCode Tests', () => { + let vscodeApp: LaunchVSCodePage; + + test.beforeAll(async () => { + const executablePath = + process.env.VSCODE_EXECUTABLE_PATH || '/usr/share/code/code'; + vscodeApp = await LaunchVSCodePage.launchVSCode(executablePath); + }); + + test.afterAll(async () => { + await vscodeApp.closeVSCode(); + }); + + test('should launch VSCode and check window title', async () => { + const window = vscodeApp.getWindow(); + const title = await window.title(); + expect(title).toContain('Visual Studio Code'); + }); + + test('should open Extensions tab and verify installed extension', async () => { + const window = vscodeApp.getWindow(); + const kaiTab = await window.getByRole('tab', { name: 'KAI', exact: true }); + await kaiTab.click(); + // Assert if KAI explorer is opened. + const title = await window.getByRole('heading', { + name: 'KAI', + exact: true, + }); + expect(title).toBeTruthy(); + }); +}); diff --git a/package.json b/package.json new file mode 100755 index 0000000..386cdd2 --- /dev/null +++ b/package.json @@ -0,0 +1,111 @@ +{ + "devDependencies": { + "@playwright/test": "^1.48.0", + "@types/node": "^22.7.5", + "electron": "^32.2.0", + "playwright-electron": "^0.5.0", + "prettier": "^3.3.3" + }, + "scripts": { + "check": "prettier --check './**/*.{ts,js,json}'", + "format": "prettier --write './**/*.{ts,js,json}'" + }, + "name": "kai-ci", + "version": "1.0.0", + "description": "This repository contains automated tests using Playwright to launch Visual Studio Code (VSCode) and install a specified extension from a VSIX file.", + "main": "index.js", + "directories": { + "test": "tests" + }, + "dependencies": { + "agent-base": "^6.0.2", + "balanced-match": "^1.0.2", + "boolean": "^3.2.0", + "brace-expansion": "^1.1.11", + "buffer-crc32": "^0.2.13", + "cacheable-lookup": "^5.0.4", + "cacheable-request": "^7.0.4", + "clone-response": "^1.0.3", + "concat-map": "^0.0.1", + "debug": "^4.3.7", + "decompress-response": "^6.0.0", + "defer-to-connect": "^2.0.1", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "detect-node": "^2.1.0", + "dotenv": "^16.4.5", + "end-of-stream": "^1.4.4", + "env-paths": "^2.2.1", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es6-error": "^4.1.1", + "escape-string-regexp": "^4.0.0", + "extract-zip": "^2.0.1", + "fd-slicer": "^1.1.0", + "fs-extra": "^8.1.0", + "fs.realpath": "^1.0.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "get-stream": "^5.2.0", + "glob": "^7.2.3", + "global-agent": "^3.0.0", + "globalthis": "^1.0.4", + "gopd": "^1.0.1", + "got": "^11.8.6", + "graceful-fs": "^4.2.11", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "hasown": "^2.0.2", + "http-cache-semantics": "^4.1.1", + "http2-wrapper": "^1.0.3", + "https-proxy-agent": "^5.0.1", + "inflight": "^1.0.6", + "inherits": "^2.0.4", + "jpeg-js": "^0.4.4", + "json-buffer": "^3.0.1", + "json-stringify-safe": "^5.0.1", + "jsonfile": "^4.0.0", + "keyv": "^4.5.4", + "lowercase-keys": "^2.0.0", + "matcher": "^3.0.0", + "mime": "^2.6.0", + "mimic-response": "^1.0.1", + "minimatch": "^3.1.2", + "ms": "^2.1.3", + "normalize-url": "^6.1.0", + "object-keys": "^1.1.1", + "once": "^1.4.0", + "p-cancelable": "^2.1.1", + "path-is-absolute": "^1.0.1", + "pend": "^1.2.0", + "playwright": "^1.48.0", + "playwright-core": "^1.48.0", + "pngjs": "^5.0.0", + "progress": "^2.0.3", + "proper-lockfile": "^4.1.2", + "proxy-from-env": "^1.1.0", + "pump": "^3.0.2", + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.2.1", + "responselike": "^2.0.1", + "retry": "^0.12.0", + "rimraf": "^3.0.2", + "roarr": "^2.15.4", + "semver": "^6.3.1", + "semver-compare": "^1.0.0", + "serialize-error": "^7.0.1", + "signal-exit": "^3.0.7", + "sprintf-js": "^1.1.3", + "sumchecker": "^3.0.1", + "type-fest": "^0.13.1", + "undici-types": "^6.19.8", + "universalify": "^0.1.2", + "wrappy": "^1.0.2", + "ws": "^7.5.10", + "yauzl": "^2.10.0" + }, + "keywords": [], + "author": "", + "license": "ISC" +} diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100755 index 0000000..05b82ba --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,19 @@ +import { defineConfig } from '@playwright/test'; +import dotenv from 'dotenv'; + +import path from 'path'; +dotenv.config({ path: path.resolve(__dirname, '.env') }); + +export default defineConfig({ + testDir: './e2e/tests', + + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + workers: process.env.CI ? 1 : undefined, + reporter: 'html', + + use: { + trace: 'on-first-retry', + }, +}); diff --git a/populate-konveyor.sh b/scripts/populate-konveyor.sh old mode 100644 new mode 100755 similarity index 100% rename from populate-konveyor.sh rename to scripts/populate-konveyor.sh