diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b997fc39d2e0c08585ea8abef7c1b23bfe0cad78..3a71fbf6504a3fa1be50999b85ae290c42b82d9c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -230,6 +230,39 @@ wp-tests: - ./nix/build-proxy.sh wp-tests <<: *coverage +ivette-tests: + stage: tests + allow_failure: true + image: "ocaml/opam:ubuntu-lts-ocaml-$OCAML" + before_script: + - sudo apt update + - sudo apt install -y xvfb curl bubblewrap unzip libnss3 libasound2 + - sudo curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash + - export NVM_DIR="$HOME/.nvm" + - '[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"' + - '[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"' + - nvm install 16 + - nvm use node 16 + - corepack enable + script: + - opam switch create 4.13.1 + - eval $(opam env) + - opam pin . -n + - opam depext frama-c --with-test + - opam install --deps-only . + - make clean && make && make -C ivette dist + - cd ivette + - cp dist/renderer/* dist/main/ + - xvfb-run --auto-servernum -e /dev/stdout -s "-screen 0 1920x1080x24 -ac -nolisten tcp -nolisten unix" dune exec -- yarn playwright test --headed + artifacts: + paths: + - ivette/tests/test-results + - ivette/screenshots + when: always + expire_in: 1 day + tags: + - docker + external-plugins: stage: tests script: diff --git a/ivette/.gitignore b/ivette/.gitignore index 08b3cc95e60a655d4d140fb6f410ec9dbedb4ebc..17050941e3b59363c860c148a3e62e7584eef111 100644 --- a/ivette/.gitignore +++ b/ivette/.gitignore @@ -18,3 +18,7 @@ yarn-error.log /icon.icns # -------------------------------------------------------------------------- +/tests/test-results/ +/screenshots/* +/playwright-report/ +/playwright/.cache/ diff --git a/ivette/Makefile b/ivette/Makefile index 5408087aa66a5362d6617a08b0898cb49371f312..1e54ceb9c4073f026e396fad918cb513429fca48 100644 --- a/ivette/Makefile +++ b/ivette/Makefile @@ -276,3 +276,21 @@ uninstall: endif # -------------------------------------------------------------------------- +# --- Ivette Tests +# -------------------------------------------------------------------------- +tests: dist + @cp dist/renderer/* dist/main/ + @echo "[Ivette] running tests" + @dune exec -- yarn playwright test + +tests-e2e: dist + @cp dist/renderer/* dist/main/ + @echo "[Ivette] running tests (e2e)" + @dune exec -- yarn playwright test tests/e2e + +tests-monkey: dist + @cp dist/renderer/* dist/main/ + @echo "[Ivette] running tests (monkey)" + @dune exec -- yarn playwright test tests/monkey + +# -------------------------------------------------------------------------- diff --git a/ivette/package.json b/ivette/package.json index 645a55b5fb89ba3008bfccb6cdce020c4c2e2d64..0e79773d104bc8a69d760e5f3fcaeb8c560e57d5 100644 --- a/ivette/package.json +++ b/ivette/package.json @@ -9,7 +9,7 @@ "description": "Frama-C GUI", "homepage": "https://frama-c.com/html/ivette.html", "scripts": { - "lint": "eslint --ext .ts,.tsx --format=compact ./src", + "lint": "eslint --ext .ts,.tsx --format=compact ./src ./tests", "typecheck": "tsc --noEmit", "typecheck:watch": "yarn run typecheck -- --watch", "build": "tsc", @@ -28,6 +28,7 @@ "@babel/preset-react": "^7.16.7", "@babel/preset-typescript": "", "@hot-loader/react-dom": "^16", + "@playwright/test": "^1.36.0", "@types/codemirror": "", "@types/cytoscape": "", "@types/estree": "^0.0.50", @@ -63,16 +64,16 @@ }, "dependencies": { "@babel/runtime": "", + "@codemirror/commands": "6.1.0", + "@codemirror/lang-cpp": "6.0.1", + "@codemirror/language": "6.2.1", + "@codemirror/search": "6.2.3", + "@codemirror/state": "6.1.1", + "@codemirror/view": "6.2.3", "@fortawesome/fontawesome-free": "", "@types/diff": "", "@types/react-window": "", "codemirror": "^5.65.2", - "@codemirror/view": "6.2.3", - "@codemirror/state": "6.1.1", - "@codemirror/search": "6.2.3", - "@codemirror/language": "6.2.1", - "@codemirror/commands": "6.1.0", - "@codemirror/lang-cpp": "6.0.1", "cytoscape": "", "cytoscape-cola": "", "cytoscape-cose-bilkent": "", @@ -82,6 +83,7 @@ "cytoscape-panzoom": "", "cytoscape-popper": "", "diff": "", + "gremlins.js": "^2.2.0", "immutable": "", "lodash": "^4.17.21", "react": "^16", diff --git a/ivette/playwright.config.ts b/ivette/playwright.config.ts new file mode 100644 index 0000000000000000000000000000000000000000..b7a7d30b501a478d25023dd13f1a76087bee16d5 --- /dev/null +++ b/ivette/playwright.config.ts @@ -0,0 +1,94 @@ +/* ************************************************************************ */ +/* */ +/* This file is part of Frama-C. */ +/* */ +/* Copyright (C) 2007-2023 */ +/* CEA (Commissariat à l'énergie atomique et aux énergies */ +/* alternatives) */ +/* */ +/* you can redistribute it and/or modify it under the terms of the GNU */ +/* Lesser General Public License as published by the Free Software */ +/* Foundation, version 2.1. */ +/* */ +/* It is distributed in the hope that it will be useful, */ +/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ +/* GNU Lesser General Public License for more details. */ +/* */ +/* See the GNU Lesser General Public License version 2.1 */ +/* for more details (enclosed in the file licenses/LGPLv2.1). */ +/* */ +/* ************************************************************************ */ + +import { defineConfig, devices } from '@playwright/test'; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// require('dotenv').config(); + +const testDir = './tests'; + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + /* Directory to look for tests when executing 'yarn playwright test'. */ + testDir: testDir, + /* Directory for test artifacts. */ + outputDir: testDir.concat('/test-results'), + /* Run tests in files in parallel (set to false to avoid concurrency issues with Electron). */ + fullyParallel: false, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only. */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: 1, + /* Reporter to use. See https://playwright.dev/docs/test-reporters. */ + reporter: [['list', { printSteps: true }], ['html', { open: 'never' }]], + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://127.0.0.1:3000', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer. */ + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers. */ + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], + + /* Run your local dev server before starting the tests. */ + // webServer: { + // command: 'npm run start', + // url: 'http://127.0.0.1:3000', + // reuseExistingServer: !process.env.CI, + // }, +}); diff --git a/ivette/src/dome/main/dome.ts b/ivette/src/dome/main/dome.ts index fde22418299c5565e527a09fd89da3ff7a1edec8..c68ef03f2674f815df9c9862332e6cd70e6a54cb 100644 --- a/ivette/src/dome/main/dome.ts +++ b/ivette/src/dome/main/dome.ts @@ -37,22 +37,22 @@ @module dome(main) */ -import _ from 'lodash'; -import fs from 'fs'; -import path from 'path'; +import installExtension, { REACT_DEVELOPER_TOOLS } from 'dome/devtools'; +import SYS, * as System from 'dome/system'; import { - app, - ipcMain, BrowserWindow, BrowserWindowConstructorOptions, IpcMainEvent, - shell, + Rectangle, + app, dialog, + ipcMain, nativeTheme, - Rectangle, + shell, } from 'electron'; -import installExtension, { REACT_DEVELOPER_TOOLS } from 'dome/devtools'; -import SYS, * as System from 'dome/system'; +import fs from 'fs'; +import _ from 'lodash'; +import path from 'path'; // -------------------------------------------------------------------------- // --- Main Window Web Navigation @@ -155,6 +155,11 @@ const APP_DIR = app.getPath('userData'); const PATH_WINDOW_SETTINGS = path.join(APP_DIR, 'WindowSettings.json'); const PATH_GLOBAL_SETTINGS = path.join(APP_DIR, 'GlobalSettings.json'); +const CLI_OPTION_SETTINGS = { + name: "--settings", + defaultValue: "DEFAULT", +} as const; + function saveGlobalSettings(): void { try { if (!fstat(APP_DIR)) fs.mkdirSync(APP_DIR); @@ -215,7 +220,10 @@ function saveWindowConfig(handle: Handle): void { storage: handle.storage, devtools: handle.devtools, }; - saveSettings(handle.config, configData); + + if (process.argv.indexOf(CLI_OPTION_SETTINGS.name) === -1) { + saveSettings(handle.config, configData); + } } function windowSyncSettings(event: IpcMainEvent): void { @@ -402,7 +410,20 @@ function createBrowserWindow( ...config, }; - const configFile = isAppWindow ? lookupConfig(wdir) : PATH_WINDOW_SETTINGS; + let configFile = PATH_WINDOW_SETTINGS; + if (argv && argv.indexOf(CLI_OPTION_SETTINGS.name) >= 0) { + const settingsIdx = argv.indexOf(CLI_OPTION_SETTINGS.name); + const settings = argv[settingsIdx + 1]; + if (settings !== CLI_OPTION_SETTINGS.defaultValue) { + configFile = argv[settingsIdx + 1]; + } + argv = argv.slice(0, settingsIdx).concat(argv.slice(settingsIdx + 2)); + } else if (isAppWindow) { + configFile = lookupConfig(wdir); + } + + console.log('[Dome] Loading config file', configFile); + const configData = loadSettings(configFile); const frame = jFrame(configData.frame); @@ -539,6 +560,15 @@ function createPrimaryWindow(): void { const wdir = cwd === '/' ? app.getPath('home') : cwd; const cmd = stripElectronArgv({ wdir, argv: process.argv }); + // Reset Settings if the associated argument is provided + const settingsIdx = cmd.argv.indexOf(CLI_OPTION_SETTINGS.name); + if (settingsIdx >= 0) { + const settings = cmd.argv[settingsIdx + 1]; + if (settings === CLI_OPTION_SETTINGS.defaultValue) { + restoreAllDefaultSettings(); + } + } + // Initialize Theme const globals = obtainGlobalSettings(); applyThemeSettings(globals); @@ -629,6 +659,23 @@ function restoreDefaultSettings(): void { broadcast('dome.ipc.settings.defaults'); } +/** + * Resets the Global setting file and delete the Window setting file + * (which will be recreated in a default state when needed) + */ +function restoreAllDefaultSettings(): void { + GlobalSettings = {}; + nativeTheme.themeSource = 'system'; + saveGlobalSettings(); + try { + if (fs.existsSync(PATH_WINDOW_SETTINGS)) { + fs.rmSync(PATH_WINDOW_SETTINGS); + } + } catch (error) { + console.warn(error); + } +} + ipcMain.on('dome.menu.settings', showSettingsWindow); ipcMain.on('dome.menu.defaults', restoreDefaultSettings); ipcMain.on('dome.app.paths', (event) => { diff --git a/ivette/tests/README.md b/ivette/tests/README.md new file mode 100644 index 0000000000000000000000000000000000000000..6e827583c84d781131b4b4abdaaa4742fc22cc36 --- /dev/null +++ b/ivette/tests/README.md @@ -0,0 +1,153 @@ +This document presents the framework for testing Ivette by describing its +overall architecture, and how to launch current tests and write new ones. + +## Overall Architecture + +The framework is based on two main testing libraries: + +- `Playwright`: <https://playwright.dev/> +- `Gremlins.js`: <https://github.com/marmelab/gremlins.js> + +`Playwright` is meant for end-to-end (e2e) testing, i.e. for testing specific +behaviors, while `Gremlins.js` provides monkey testing, i.e. robustness/stress +testing of the entire application. + +Here is how the framework is organized: + +- `e2e/` contains the end-to-end tests, +- `monkey/` cointains the monkey tests, +- `lib/` contains utilities for writing tests using the `Playwright` + infrastructure. These utilities are used both for e2e and monkey testing. + +## Executing tests + +**Requirement:** In order to execute the tests, Frama-C has to be built +beforehand. + +Testing Ivette, in terms of both the e2e and monkey tests, amounts to the +execution of the following commands from the Frama-C root directory: + +```sh +$ make // builds Frama-C, if not already +$ cd ivette +$ make tests // builds Ivette and execute all tests +``` + +To execute the e2e tests only, the last command to execute is the following: + +```sh +$ make tests-e2e +``` + +To execute the monkey tests only, the last command to execute is the following: + +```sh +$ make tests-monkey +``` + +## Executing tests (Advanced) + +Whenever the execution of the tests does not need (re-)building Ivette, one can +directly do the following from the Ivette root directory: + +```sh +$ dune exec -- yarn playwright test +``` + +This is also useful for executing just a (list of) test(s). In such a case, do +the following from the Ivette root directory: + +```sh +$ dune exec -- yarn playwright test /path/to/test1 /path/to/test2 +``` + +For more information, refer to the `Playwright` +[documentation](https://playwright.dev/docs/running-tests). + +## Accessing tests report + +Upon a test failure, `Playwright` informs about the failing step, or assertion, +with a message on the console. This should be sufficient to debug the failure. +However, `Playwright` also writes down a report of the failure that can be +accessed as follows from the Ivette root directory: + +```sh +$ yarn playwright show-report +``` + +The previous command should open a browser tab showing the last recorded report; +if not, such a report should be available at `http://localhost:9323`. + +## Writing end-to-end tests + +End-to-end testing is meant for testing specific Ivette behaviors. This is done +by using the `Playwright` library, which is based on [expect +assertionts](https://playwright.dev/docs/test-assertions) on elements of a web +page at a given moment, named +[locators](https://playwright.dev/docs/api/class-locator). + +A test for Ivette looks like this: + +```ts +import { test } from "@playwright/test"; +import * as e2eService from "../libs/e2eService"; + +test("<test description>", async () => { + const launchAppResult = await e2eService.launchApp( + <arguments>, + ); + + const electronApp = launchAppResult.app; + const window = launchAppResult.page; + + // find/select HTML element via Playwright's + // [locators](https://playwright.dev/docs/locators]. + const locator = await window.<locator>; + + // perform a Playwright's [action](https://playwright.dev/docs/input) + // on the locator. + await locator.<action>; + + // [test assertion](https://playwright.dev/docs/test-assertions) on a + // Playwright's value (e.g. locator) with respect to a Playwright's matcher. + await expect(<value>).<matcher>; + + // exit app. + await electronApp.close(); +}); +``` + +The Ivette testing framework abstracts the `Playwright`'s API by providing +common utilities in the sub-directory `/tests/libs`. In particular, +`/tests/libs/e2eService.ts` provides, and should be enriched with, common +testing services for Ivette, while `/tests/libs/locatorsUtil.ts` provides, and +should be enriched with, common locators for Ivette. + +Note that `/tests/libs/e2eService.ts` also provides with some examples of +`<arguments>` to fed `e2eService.launchApp()` function with. In particular, use +the `Dome`'s `--settings CLEAN` option for launching Ivette with default +settings, and the `--settings </path/to/json-file-with-settings>` +option for launching Ivette with particular settings. + +`Playwright` also provides an interactive way for identifying new locators. +Adding `await window.pause()` inside a test will open an additional `Playwright` +window with a `Pick locator` button at the bottom, which provides the necessary +locator any selected element (i.e., it works similarly to the inspect +functionality of most browser developer tools). + +Complete test scenarios are provided in the sub-directory `/tests/e2e/`. + +## Monkey Testing + +Monkey testing is meant for testing the Ivette robustness by performing a stress +testing of the UI. This is done by using the `gremlins.js` library. + +Please refer to the [library +documentation](https://github.com/marmelab/gremlins.js) for more details, or +look at the `/tests/monkey/monkey-testing.spec.ts` for an example. + +Note that, to obtain reproducible tests, one should seed a randomizer (see the +`randomizer` value in `monkey-testing.spec.ts` for an example). For that, one +should also launch Ivette with either default settings, by using the +`-settings CLEAN` option, or fixed settings, by using the `--settings +</path/to/json-file-with-settings>` option. diff --git a/ivette/tests/e2e/launch-app.spec.ts b/ivette/tests/e2e/launch-app.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..e9e2ce889fdec3443b3bdb9d82d2fe9f0757961d --- /dev/null +++ b/ivette/tests/e2e/launch-app.spec.ts @@ -0,0 +1,37 @@ +/* ************************************************************************ */ +/* */ +/* This file is part of Frama-C. */ +/* */ +/* Copyright (C) 2007-2023 */ +/* CEA (Commissariat à l'énergie atomique et aux énergies */ +/* alternatives) */ +/* */ +/* you can redistribute it and/or modify it under the terms of the GNU */ +/* Lesser General Public License as published by the Free Software */ +/* Foundation, version 2.1. */ +/* */ +/* It is distributed in the hope that it will be useful, */ +/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ +/* GNU Lesser General Public License for more details. */ +/* */ +/* See the GNU Lesser General Public License version 2.1 */ +/* for more details (enclosed in the file licenses/LGPLv2.1). */ +/* */ +/* ************************************************************************ */ + +import { test } from "@playwright/test"; +import * as e2eService from "../libs/e2eService"; + +test("launch app", async () => { + const launchAppResult = await e2eService.launchApp( + e2eService.argsDefaultLaunch, + ); + const electronApp = launchAppResult.app; + const window = launchAppResult.page; + + await window.screenshot({ path: "screenshots/e2e-app-launch.png" }); + + // Exit app. + await electronApp.close(); +}); diff --git a/ivette/tests/e2e/server-connection-file.spec.ts b/ivette/tests/e2e/server-connection-file.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..7a379f024a14cc732092aaa3e8f879659dcda42a --- /dev/null +++ b/ivette/tests/e2e/server-connection-file.spec.ts @@ -0,0 +1,37 @@ +/* ************************************************************************ */ +/* */ +/* This file is part of Frama-C. */ +/* */ +/* Copyright (C) 2007-2023 */ +/* CEA (Commissariat à l'énergie atomique et aux énergies */ +/* alternatives) */ +/* */ +/* you can redistribute it and/or modify it under the terms of the GNU */ +/* Lesser General Public License as published by the Free Software */ +/* Foundation, version 2.1. */ +/* */ +/* It is distributed in the hope that it will be useful, */ +/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ +/* GNU Lesser General Public License for more details. */ +/* */ +/* See the GNU Lesser General Public License version 2.1 */ +/* for more details (enclosed in the file licenses/LGPLv2.1). */ +/* */ +/* ************************************************************************ */ + +import { test } from "@playwright/test"; +import * as e2eService from "../libs/e2eService"; + +test("server connection with a C file to analyze", async () => { + const launchAppResult = await e2eService.launchApp( + e2eService.argsLaunchWithTestFile, + ); + const electronApp = launchAppResult.app; + const window = launchAppResult.page; + + await e2eService.testFileIsLoaded(window); + + // Exit app. + await electronApp.close(); +}); diff --git a/ivette/tests/e2e/server-connection.spec.ts b/ivette/tests/e2e/server-connection.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..263384fd1ce7aaf0b5eae20a878977a8633a55ef --- /dev/null +++ b/ivette/tests/e2e/server-connection.spec.ts @@ -0,0 +1,37 @@ +/* ************************************************************************ */ +/* */ +/* This file is part of Frama-C. */ +/* */ +/* Copyright (C) 2007-2023 */ +/* CEA (Commissariat à l'énergie atomique et aux énergies */ +/* alternatives) */ +/* */ +/* you can redistribute it and/or modify it under the terms of the GNU */ +/* Lesser General Public License as published by the Free Software */ +/* Foundation, version 2.1. */ +/* */ +/* It is distributed in the hope that it will be useful, */ +/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ +/* GNU Lesser General Public License for more details. */ +/* */ +/* See the GNU Lesser General Public License version 2.1 */ +/* for more details (enclosed in the file licenses/LGPLv2.1). */ +/* */ +/* ************************************************************************ */ + +import { test } from "@playwright/test"; +import * as e2eService from "../libs/e2eService"; + +test("check server connection", async () => { + const launchAppResult = await e2eService.launchApp( + e2eService.argsLaunchWithDefaultSettings, + ); + const electronApp = launchAppResult.app; + const window = launchAppResult.page; + + await e2eService.testServerIsStarted(window); + + // Exit app. + await electronApp.close(); +}); diff --git a/ivette/tests/libs/e2eService.ts b/ivette/tests/libs/e2eService.ts new file mode 100644 index 0000000000000000000000000000000000000000..1de42c07e52ebf15a202b03ec1c6b212a9447494 --- /dev/null +++ b/ivette/tests/libs/e2eService.ts @@ -0,0 +1,108 @@ +/* ************************************************************************ */ +/* */ +/* This file is part of Frama-C. */ +/* */ +/* Copyright (C) 2007-2023 */ +/* CEA (Commissariat à l'énergie atomique et aux énergies */ +/* alternatives) */ +/* */ +/* you can redistribute it and/or modify it under the terms of the GNU */ +/* Lesser General Public License as published by the Free Software */ +/* Foundation, version 2.1. */ +/* */ +/* It is distributed in the hope that it will be useful, */ +/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ +/* GNU Lesser General Public License for more details. */ +/* */ +/* See the GNU Lesser General Public License version 2.1 */ +/* for more details (enclosed in the file licenses/LGPLv2.1). */ +/* */ +/* ************************************************************************ */ + +import { ElectronApplication, Page, expect } from "@playwright/test"; +import { _electron as electron } from "playwright-core"; +import * as locs from "./locatorsUtil"; + +/** + * Basic Electron configuration for Playwright e2e tests of Ivette + */ +export const argsDefaultLaunch: string[] = [ + "./dist/main/main.js", + "--no-sandbox", +]; + +/** + * Electron configuration for Playwright e2e tests of Ivette's default settings + */ +export const argsLaunchWithDefaultSettings: string[] = [ + "./dist/main/main.js", + "--no-sandbox", + "--settings", + "DEFAULT" +]; + +/** + * Electron configuration for Playwright e2e tests of Ivette on a C file + */ +export const argsLaunchWithTestFile: string[] = [ + "./dist/main/main.js", + "--no-sandbox", + "--settings", + "./tests/settings.json", + "../tests/test/adpcm.c", +]; + +/** + * Basic Electron launch of Ivette for Playwright e2e tests + */ +export async function launchApp( + params: string[] +): Promise<{ app: ElectronApplication; page: Page }> { + const electronApp = await electron.launch({ + env: { + ...process.env, + NODE_ENV: "development", + }, + args: params, + }); + + // Get the first window that the app opens, wait if necessary + const window = await electronApp.firstWindow(); + + return { + app: electronApp, + page: window, + }; +} + +export async function testServerIsStarted(window: Page): Promise<void> { + // Click on the Console tab in the right menu + await locs.getConsoleMenuItem(window).click(); + + // Check the server status in the header's button bar + await expect(locs.getStartServerButton(window)).toBeDisabled(); + await expect(locs.getShutDownServerButton(window)).toBeEnabled(); + + // Check the server status in the console view + await expect( + locs.getConsoleView(window).getByText("[server] Socket server running.") + ).toBeVisible(); + + // Check the server status in the footer + await expect(locs.getServerStatusLabel(window)).toHaveText("ON"); +} + +export async function testFileIsLoaded(window: Page): Promise<void> { + await locs.getConsoleMenuItem(window).click(); + // Check if a message is present in the console view to confirm the file is + // loaded + await expect( + locs.getConsoleView(window).getByText("adpcm.c (with preprocessing)") + ).toBeVisible(); + + // Check if the main function is visible in the functions view + await expect( + locs.getFunctionsSideBar(window).getByText("main", { exact: true }) + ).toBeVisible(); +} diff --git a/ivette/tests/libs/locatorsUtil.ts b/ivette/tests/libs/locatorsUtil.ts new file mode 100644 index 0000000000000000000000000000000000000000..7d57f5843c6e331941e9620a06509ea7602dc3f1 --- /dev/null +++ b/ivette/tests/libs/locatorsUtil.ts @@ -0,0 +1,67 @@ +/* ************************************************************************ */ +/* */ +/* This file is part of Frama-C. */ +/* */ +/* Copyright (C) 2007-2023 */ +/* CEA (Commissariat à l'énergie atomique et aux énergies */ +/* alternatives) */ +/* */ +/* you can redistribute it and/or modify it under the terms of the GNU */ +/* Lesser General Public License as published by the Free Software */ +/* Foundation, version 2.1. */ +/* */ +/* It is distributed in the hope that it will be useful, */ +/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ +/* GNU Lesser General Public License for more details. */ +/* */ +/* See the GNU Lesser General Public License version 2.1 */ +/* for more details (enclosed in the file licenses/LGPLv2.1). */ +/* */ +/* ************************************************************************ */ + +import { Locator, Page } from "@playwright/test"; + +/** + * Locator to select "Console" in the right menu + */ +export function getConsoleMenuItem(window: Page): Locator { + return window + .getByText("ViewsConsoleSource Code") + .getByText("Console") + .first(); +} + +/** + * Locator to select the Start button in the top button bar + */ +export function getStartServerButton(window: Page): Locator { + return window + .locator(".dome-xToolBar") + .getByRole("button", { name: "Start the server", exact: true }); +} + +/** + * Locator to select the Shut Down button in the top button bar + */ +export function getShutDownServerButton(window: Page): Locator { + return window.locator(".dome-xToolBar").getByTitle("Shut down the server"); +} + +/** + * Locator to select the Console View + */ +export function getConsoleView(window: Page): Locator { + return window.locator(".CodeMirror"); +} + +/** + * Locator to select the Functions side bar when loading a file + */ +export function getFunctionsSideBar(window: Page): Locator { + return window.locator(".dome-xSideBar").first(); +} + +export function getServerStatusLabel(window: Page): Locator { + return window.getByTitle("Server is running"); +} diff --git a/ivette/tests/monkey/monkey-testing.spec.ts b/ivette/tests/monkey/monkey-testing.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..0e5d7b9e3e5c19beaef0cad6553f43dae4bf3ea8 --- /dev/null +++ b/ivette/tests/monkey/monkey-testing.spec.ts @@ -0,0 +1,81 @@ +/* ************************************************************************ */ +/* */ +/* This file is part of Frama-C. */ +/* */ +/* Copyright (C) 2007-2023 */ +/* CEA (Commissariat à l'énergie atomique et aux énergies */ +/* alternatives) */ +/* */ +/* you can redistribute it and/or modify it under the terms of the GNU */ +/* Lesser General Public License as published by the Free Software */ +/* Foundation, version 2.1. */ +/* */ +/* It is distributed in the hope that it will be useful, */ +/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ +/* GNU Lesser General Public License for more details. */ +/* */ +/* See the GNU Lesser General Public License version 2.1 */ +/* for more details (enclosed in the file licenses/LGPLv2.1). */ +/* */ +/* ************************************************************************ */ + +import { test } from "@playwright/test"; +import * as e2eService from "../libs/e2eService"; + +/* eslint-disable @typescript-eslint/no-explicit-any */ +let gremlins: any; + +test("run gremlins.js", async () => { + test.slow(); // long timeout for a very slow test. + + const launchAppResult = await e2eService.launchApp( + e2eService.argsLaunchWithTestFile, + ); + const electronApp = launchAppResult.app; + const window = launchAppResult.page; + + // await window.pause() + + // Load gremlins.js script + await window.addScriptTag( + { path: "./node_modules/gremlins.js/dist/gremlins.min.js", } + ); + + // Launch gremlins.js with default settings + // await window.evaluate(() => gremlins.createHorde().unleash()); + + // Launch gremlins.js with custom settings + // See https://github.com/marmelab/gremlins.js for more info + + await window.evaluate(async () => { + await gremlins + .createHorde({ + species: [ + gremlins.species.clicker(), // clicks anywhere on the visible area of the document + gremlins.species.toucher(), // touches anywhere on the visible area of the document + gremlins.species.scroller(), // scrolls the viewport to reveal another part of the document + gremlins.species.typer(), // types keys on the keyboard + gremlins.species.formFiller(), // fills forms by entering data, selecting options, clicking checkboxes, etc + ], + mogwais: [ + gremlins.mogwais.alert(), // prevents calls to alert() from blocking the test + gremlins.mogwais.fps(), // logs the number of frames per seconds (FPS) of the browser + ], + strategies: [ + gremlins.strategies.distribution({ + distribution: [0.3, 0.3, 0.3, 0.1, 0.1], // the first three gremlins have more chances to be executed than the last + delay: 10, // wait 10 ms between each action + }), + ], + randomizer: new gremlins.Chance(1234), // if you want the attack to be repeatable, you need to seed the random number generator + }) + .unleash(); + }); + + // Could be useful if you want to review the logs + // await window.pause() + + // Exit app. + await electronApp.close(); +}); diff --git a/ivette/tests/settings.json b/ivette/tests/settings.json new file mode 100644 index 0000000000000000000000000000000000000000..746bf14e7edaad3df59e5cf9cfedb4754369991a --- /dev/null +++ b/ivette/tests/settings.json @@ -0,0 +1,38 @@ +{ + "frame": { + "x": 100, + "y": 100, + "width": 1900, + "height": 1060 + }, + "settings": { + "frama-c.labview.shape": { + "kind": "VBOX", + "content": [ + { + "kind": "HBOX", + "content": [ + { + "kind": "ITEM", + "id": "frama-c.astview" + }, + { + "kind": "ITEM", + "id": "frama-c.sourcecode" + } + ] + }, + { + "kind": "ITEM", + "id": "frama-c.astinfo" + } + ] + }, + "frama-c.labview.panel.views.current": "builtin.views.source" + }, + "storage": { + "Controller.history": [ + ] + }, + "devtools": false +} diff --git a/ivette/tsconfig.json b/ivette/tsconfig.json index 5674a51edb47c9a247465452802fe6ebbc8c1350..0516d57b747b191ed288c55fa69ad7fac3feb49a 100644 --- a/ivette/tsconfig.json +++ b/ivette/tsconfig.json @@ -80,7 +80,8 @@ }, "include": [ "src/**/*", - "api/**/*" + "api/**/*", + "tests/**/*", ], "exclude": [ "node_modules", diff --git a/ivette/yarn.lock b/ivette/yarn.lock index 22ba43191184c39b0b20f9967f2f8123539d8b29..21b0736040dc934879e1b6505aa4ad64256ccc6a 100644 --- a/ivette/yarn.lock +++ b/ivette/yarn.lock @@ -2039,6 +2039,16 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@playwright/test@^1.36.0": + version "1.36.0" + resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.36.0.tgz#df2c0b09bbd27016adf1892b0c3502c4ce88d307" + integrity sha512-yN+fvMYtiyLFDCQos+lWzoX4XW3DNuaxjBu68G0lkgLgC6BP+m/iTxJQoSicz/x2G5EsrqlZTqTIP9sTgLQerg== + dependencies: + "@types/node" "*" + playwright-core "1.36.0" + optionalDependencies: + fsevents "2.3.2" + "@popperjs/core@^2.0.0": version "2.11.0" resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.0.tgz#6734f8ebc106a0860dff7f92bf90df193f0935d7" @@ -5382,6 +5392,11 @@ fs.realpath@^1.0.0: resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= +fsevents@2.3.2, fsevents@~2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + fsevents@^1.2.7: version "1.2.13" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.13.tgz#f325cb0455592428bcf11b383370ef70e3bfcc38" @@ -5390,11 +5405,6 @@ fsevents@^1.2.7: bindings "^1.5.0" nan "^2.12.1" -fsevents@~2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" - integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== - function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" @@ -7727,6 +7737,11 @@ pkg-dir@^4.1.0: dependencies: find-up "^4.0.0" +playwright-core@1.36.0: + version "1.36.0" + resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.36.0.tgz#35d1ed5f364a31e58bc8f06688ab02d538b96eb6" + integrity sha512-7RTr8P6YJPAqB+8j5ATGHqD6LvLLM39sYVNsslh78g8QeLcBs5750c6+msjrHUwwGt+kEbczBj1XB22WMwn+WA== + plist@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/plist/-/plist-3.0.4.tgz#a62df837e3aed2bb3b735899d510c4f186019cbe"