diff --git a/bun.lockb b/bun.lockb index 0645c92..69cacb8 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 4e04928..2bde455 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,8 @@ "preview": "vite preview", "dist": "vite build", "count-source-lines": "exec scripts/src-lc.sh", - "typecheck": "tsc --noEmit --skipLibCheck" + "typecheck": "tsc --noEmit --skipLibCheck", + "wdio": "wdio run ./wdio.conf.ts" }, "keywords": [], "author": "Rubicon", @@ -18,17 +19,26 @@ "devDependencies": { "@solid-devtools/overlay": "^0.30.1", "@suid/vite-plugin": "^0.3.1", + "@testing-library/webdriverio": "^3.2.1", "@types/hammerjs": "^2.0.46", "@types/masonry-layout": "^4.2.8", "@vite-pwa/assets-generator": "^0.2.6", + "@wdio/cli": "^9.4.5", + "@wdio/lighthouse-service": "^9.4.5", + "@wdio/local-runner": "^9.4.5", + "@wdio/mocha-framework": "^9.4.4", + "@wdio/spec-reporter": "^9.4.4", "postcss": "^8.4.49", "prettier": "^3.3.3", + "tsx": "^4.19.2", "typescript": "^5.6.3", "vite": "^5.4.11", "vite-plugin-package-version": "^1.1.0", "vite-plugin-pwa": "^0.20.5", "vite-plugin-solid": "^2.10.2", "vite-plugin-solid-styled": "^0.11.1", + "wdio-vite-service": "^2.0.0", + "wdio-wait-for": "^3.0.11", "workbox-build": "^7.3.0", "wrangler": "^3.86.1" }, diff --git a/test/objects/accounts.ts b/test/objects/accounts.ts new file mode 100644 index 0000000..ff2c30b --- /dev/null +++ b/test/objects/accounts.ts @@ -0,0 +1,25 @@ +import { $, browser } from "@wdio/globals"; +import Page from "./page.js"; + +/** + * sub page containing specific selectors and methods for a specific page + */ +export class SignInPage extends Page { + public static get serverUrlInput() { + return $("[name=serverUrl]"); + } + + public static async beTheCurrentPage() { + const urlMatched = + new URL(await browser.getUrl()).pathname === "/accounts/sign-in"; + const elementShowed = await this.serverUrlInput.isDisplayed(); + return urlMatched && elementShowed; + } + + /** + * overwrite specific options to adapt it to page object + */ + public static async open() { + return await super.open("accounts/sign-in"); + } +} diff --git a/test/objects/page.ts b/test/objects/page.ts new file mode 100644 index 0000000..67c03b4 --- /dev/null +++ b/test/objects/page.ts @@ -0,0 +1,15 @@ +import { browser } from "@wdio/globals"; + +/** + * main page object containing all methods, selectors and functionality + * that is shared across all page objects + */ +export default class Page { + /** + * Opens a sub page of the page + * @param path path of the sub page (e.g. /path/to/page.html) + */ + public static open(path: string) { + return browser.url(`/${path}`); + } +} diff --git a/test/objects/timelines.ts b/test/objects/timelines.ts new file mode 100644 index 0000000..5562da0 --- /dev/null +++ b/test/objects/timelines.ts @@ -0,0 +1,7 @@ +import Page from "./page"; + +export class IndexPage extends Page { + public static async open(path: string) { + return await super.open(path); + } +} diff --git a/test/sign-in.spec.ts b/test/sign-in.spec.ts new file mode 100644 index 0000000..c1ac93d --- /dev/null +++ b/test/sign-in.spec.ts @@ -0,0 +1,11 @@ +import { expect } from "@wdio/globals"; +import { SignInPage } from "./objects/accounts.js"; +import { IndexPage } from "./objects/timelines.js"; + +describe("The index page", () => { + it("jumps to the sign-in page if no account signed in", async () => { + await IndexPage.open(""); + + expect(await browser.waitUntil(SignInPage.beTheCurrentPage)); + }); +}); diff --git a/test/tsconfig.json b/test/tsconfig.json new file mode 100644 index 0000000..753feaf --- /dev/null +++ b/test/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": ["../tsconfig.super.json"], + "compilerOptions": { + "types": [ + "node", + "@wdio/globals/types", + "@wdio/mocha-framework", + "@wdio/lighthouse-service" + ] + }, + "include": ["./**/*.ts", "../wdio.conf.ts"] +} diff --git a/tsconfig.json b/tsconfig.json index feaab58..90a351f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,6 +6,6 @@ "lib": ["ESNext", "DOM", "DOM.Iterable"] }, "include": ["./src/**/*.ts", "./src/**/*.tsx", "./*.ts", "./types/**.ts"], - "exclude": ["./src/serviceworker/**"], + "exclude": ["./src/serviceworker/**", "./wdio.conf.ts"], "extends": ["./tsconfig.super.json"] } diff --git a/wdio.conf.ts b/wdio.conf.ts new file mode 100644 index 0000000..1655053 --- /dev/null +++ b/wdio.conf.ts @@ -0,0 +1,327 @@ +const chromeDefaultOpts = process.env.CI + ? { + args: ["headless", "disable-gpu"], + } + : {}; + +const firefoxDefaultOpts = process.env.CI + ? { + args: ["-headless"], + } + : {}; + +export const config: WebdriverIO.Config = { + // + // ==================== + // Runner Configuration + // ==================== + // WebdriverIO supports running e2e tests as well as unit and component tests. + runner: "local", + tsConfigPath: "./test/tsconfig.json", + + // + // ================== + // Specify Test Files + // ================== + // Define which test specs should run. The pattern is relative to the directory + // of the configuration file being run. + // + // The specs are defined as an array of spec files (optionally using wildcards + // that will be expanded). The test for each spec file will be run in a separate + // worker process. In order to have a group of spec files run in the same worker + // process simply enclose them in an array within the specs array. + // + // The path of the spec files will be resolved relative from the directory of + // of the config file unless it's absolute. + // + specs: ["./test/**/*.spec.ts"], + // Patterns to exclude. + exclude: ["./test/objects/**/*"], + // + // ============ + // Capabilities + // ============ + // Define your capabilities here. WebdriverIO can run multiple capabilities at the same + // time. Depending on the number of capabilities, WebdriverIO launches several test + // sessions. Within your capabilities you can overwrite the spec and exclude options in + // order to group specific specs to a specific capability. + // + // First, you can define how many instances should be started at the same time. Let's + // say you have 3 different capabilities (Chrome, Firefox, and Safari) and you have + // set maxInstances to 1; wdio will spawn 3 processes. Therefore, if you have 10 spec + // files and you set maxInstances to 10, all spec files will get tested at the same time + // and 30 processes will get spawned. The property handles how many capabilities + // from the same test should run tests. + // + maxInstances: 1, + // + // If you have trouble getting all important capabilities together, check out the + // Sauce Labs platform configurator - a great tool to configure your capabilities: + // https://saucelabs.com/platform/platform-configurator + // + capabilities: [ + { + browserName: "chrome", + "goog:chromeOptions": chromeDefaultOpts, + }, + // Could not find a reliable way to use old chrome, + // skipped here. + { + browserName: "firefox", + "moz:firefoxOptions": firefoxDefaultOpts, + }, + { + browserName: "firefox", + browserVersion: "esr_115.18.0esr", + "moz:firefoxOptions": firefoxDefaultOpts, + }, + ...(process.env.TEST_SAFARI + ? [ + { + browserName: "safari", + }, + { + browserName: "safari technology preview", + }, + ] + : []), + ], + + // + // =================== + // Test Configurations + // =================== + // Define all options that are relevant for the WebdriverIO instance here + // + // Level of logging verbosity: trace | debug | info | warn | error | silent + logLevel: "info", + // + // Set specific log levels per logger + // loggers: + // - webdriver, webdriverio + // - @wdio/browserstack-service, @wdio/lighthouse-service, @wdio/sauce-service + // - @wdio/mocha-framework, @wdio/jasmine-framework + // - @wdio/local-runner + // - @wdio/sumologic-reporter + // - @wdio/cli, @wdio/config, @wdio/utils + // Level of logging verbosity: trace | debug | info | warn | error | silent + // logLevels: { + // webdriver: 'info', + // '@wdio/appium-service': 'info' + // }, + // + // If you only want to run your tests until a specific amount of tests have failed use + // bail (default is 0 - don't bail, run all tests). + bail: 0, + // + // Set a base URL in order to shorten url command calls. If your `url` parameter starts + // with `/`, the base url gets prepended, not including the path portion of your baseUrl. + // If your `url` parameter starts without a scheme or `/` (like `some/path`), the base url + // gets prepended directly. + // baseUrl: 'http://localhost:4698', + // + // Default timeout for all waitFor* commands. + waitforTimeout: 10000, + // + // Default timeout in milliseconds for request + // if browser driver or grid doesn't send response + connectionRetryTimeout: 120000, + // + // Default request retries count + connectionRetryCount: 3, + // + // Test runner services + // Services take over a specific job you don't want to take care of. They enhance + // your test setup with almost no effort. Unlike plugins, they don't add new + // commands. Instead, they hook themselves up into the test process. + services: ["vite", "lighthouse"], + + // Framework you want to run your specs with. + // The following are supported: Mocha, Jasmine, and Cucumber + // see also: https://webdriver.io/docs/frameworks + // + // Make sure you have the wdio adapter package for the specific framework installed + // before running any tests. + framework: "mocha", + + // + // The number of times to retry the entire specfile when it fails as a whole + // specFileRetries: 1, + // + // Delay in seconds between the spec file retry attempts + // specFileRetriesDelay: 0, + // + // Whether or not retried spec files should be retried immediately or deferred to the end of the queue + // specFileRetriesDeferred: false, + // + // Test reporter for stdout. + // The only one supported by default is 'dot' + // see also: https://webdriver.io/docs/dot-reporter + reporters: ["spec"], + + // Options to be passed to Mocha. + // See the full list at http://mochajs.org/ + mochaOpts: { + ui: "bdd", + timeout: 60000, + }, + + // + // ===== + // Hooks + // ===== + // WebdriverIO provides several hooks you can use to interfere with the test process in order to enhance + // it and to build services around it. You can either apply a single function or an array of + // methods to it. If one of them returns with a promise, WebdriverIO will wait until that promise got + // resolved to continue. + /** + * Gets executed once before all workers get launched. + * @param {object} config wdio configuration object + * @param {Array.} capabilities list of capabilities details + */ + // onPrepare: function (config, capabilities) { + // }, + /** + * Gets executed before a worker process is spawned and can be used to initialize specific service + * for that worker as well as modify runtime environments in an async fashion. + * @param {string} cid capability id (e.g 0-0) + * @param {object} caps object containing capabilities for session that will be spawn in the worker + * @param {object} specs specs to be run in the worker process + * @param {object} args object that will be merged with the main configuration once worker is initialized + * @param {object} execArgv list of string arguments passed to the worker process + */ + // onWorkerStart: function (cid, caps, specs, args, execArgv) { + // }, + /** + * Gets executed just after a worker process has exited. + * @param {string} cid capability id (e.g 0-0) + * @param {number} exitCode 0 - success, 1 - fail + * @param {object} specs specs to be run in the worker process + * @param {number} retries number of retries used + */ + // onWorkerEnd: function (cid, exitCode, specs, retries) { + // }, + /** + * Gets executed just before initialising the webdriver session and test framework. It allows you + * to manipulate configurations depending on the capability or spec. + * @param {object} config wdio configuration object + * @param {Array.} capabilities list of capabilities details + * @param {Array.} specs List of spec file paths that are to be run + * @param {string} cid worker id (e.g. 0-0) + */ + // beforeSession: function (config, capabilities, specs, cid) { + // }, + /** + * Gets executed before test execution begins. At this point you can access to all global + * variables like `browser`. It is the perfect place to define custom commands. + * @param {Array.} capabilities list of capabilities details + * @param {Array.} specs List of spec file paths that are to be run + * @param {object} browser instance of created browser/device session + */ + // before: function (capabilities, specs) { + // }, + /** + * Runs before a WebdriverIO command gets executed. + * @param {string} commandName hook command name + * @param {Array} args arguments that command would receive + */ + // beforeCommand: function (commandName, args) { + // }, + /** + * Hook that gets executed before the suite starts + * @param {object} suite suite details + */ + // beforeSuite: function (suite) { + // }, + /** + * Function to be executed before a test (in Mocha/Jasmine) starts. + */ + // beforeTest: function (test, context) { + // }, + /** + * Hook that gets executed _before_ a hook within the suite starts (e.g. runs before calling + * beforeEach in Mocha) + */ + // beforeHook: function (test, context, hookName) { + // }, + /** + * Hook that gets executed _after_ a hook within the suite starts (e.g. runs after calling + * afterEach in Mocha) + */ + // afterHook: function (test, context, { error, result, duration, passed, retries }, hookName) { + // }, + /** + * Function to be executed after a test (in Mocha/Jasmine only) + * @param {object} test test object + * @param {object} context scope object the test was executed with + * @param {Error} result.error error object in case the test fails, otherwise `undefined` + * @param {*} result.result return object of test function + * @param {number} result.duration duration of test + * @param {boolean} result.passed true if test has passed, otherwise false + * @param {object} result.retries information about spec related retries, e.g. `{ attempts: 0, limit: 0 }` + */ + // afterTest: function(test, context, { error, result, duration, passed, retries }) { + // }, + + /** + * Hook that gets executed after the suite has ended + * @param {object} suite suite details + */ + // afterSuite: function (suite) { + // }, + /** + * Runs after a WebdriverIO command gets executed + * @param {string} commandName hook command name + * @param {Array} args arguments that command would receive + * @param {number} result 0 - command success, 1 - command error + * @param {object} error error object if any + */ + // afterCommand: function (commandName, args, result, error) { + // }, + /** + * Gets executed after all tests are done. You still have access to all global variables from + * the test. + * @param {number} result 0 - test pass, 1 - test fail + * @param {Array.} capabilities list of capabilities details + * @param {Array.} specs List of spec file paths that ran + */ + // after: function (result, capabilities, specs) { + // }, + /** + * Gets executed right after terminating the webdriver session. + * @param {object} config wdio configuration object + * @param {Array.} capabilities list of capabilities details + * @param {Array.} specs List of spec file paths that ran + */ + // afterSession: function (config, capabilities, specs) { + // }, + /** + * Gets executed after all workers got shut down and the process is about to exit. An error + * thrown in the onComplete hook will result in the test run failing. + * @param {object} exitCode 0 - success, 1 - fail + * @param {object} config wdio configuration object + * @param {Array.} capabilities list of capabilities details + * @param {} results object containing test results + */ + // onComplete: function(exitCode, config, capabilities, results) { + // }, + /** + * Gets executed when a refresh happens. + * @param {string} oldSessionId session ID of the old session + * @param {string} newSessionId session ID of the new session + */ + // onReload: function(oldSessionId, newSessionId) { + // } + /** + * Hook that gets executed before a WebdriverIO assertion happens. + * @param {object} params information about the assertion to be executed + */ + // beforeAssertion: function(params) { + // } + /** + * Hook that gets executed after a WebdriverIO assertion happened. + * @param {object} params information about the assertion that was executed, including its results + */ + // afterAssertion: function(params) { + // } +};