diff --git a/.gitignore b/.gitignore index 9eb5b266ffc79..b71f00f4cc582 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ __coverage__ csak-timelog.json .idea/ debug/ +test-results/ # Lefthook lefthook-local.yml diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 5dc5d72c0a43a..802674aad7c42 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -6,6 +6,7 @@ "syler.sass-indented", "redhat.vscode-yaml", "vue.volar", - "eamodio.gitlens" + "eamodio.gitlens", + "ms-playwright.playwright" ] } diff --git a/_scripts/clean.mjs b/_scripts/clean.mjs index b4ac899f8d303..aee867d1178de 100644 --- a/_scripts/clean.mjs +++ b/_scripts/clean.mjs @@ -3,8 +3,10 @@ import { join } from 'path' const BUILD_PATH = join(import.meta.dirname, '..', 'build') const DIST_PATH = join(import.meta.dirname, '..', 'dist') +const TEST_RESULTS_PATH = join(import.meta.dirname, '..', 'test-results') await Promise.all([ rm(BUILD_PATH, { recursive: true, force: true }), - rm(DIST_PATH, { recursive: true, force: true }) + rm(DIST_PATH, { recursive: true, force: true }), + rm(TEST_RESULTS_PATH, { recursive: true, force: true }), ]) diff --git a/_scripts/webpack.main.config.js b/_scripts/webpack.main.config.js index 86f77e9ad22b2..b87f3823d1e1b 100644 --- a/_scripts/webpack.main.config.js +++ b/_scripts/webpack.main.config.js @@ -5,74 +5,77 @@ const JsonMinimizerPlugin = require('json-minimizer-webpack-plugin') const isDevMode = process.env.NODE_ENV === 'development' -/** @type {import('webpack').Configuration} */ -const config = { - name: 'main', - mode: process.env.NODE_ENV, - devtool: isDevMode ? 'eval-cheap-module-source-map' : false, - entry: { - main: path.join(__dirname, '../src/main/index.js'), - }, - module: { - rules: [ - { - test: /\.js$/, - use: 'babel-loader', - exclude: /node_modules/, - }, - { - resource: path.resolve(__dirname, '../node_modules/mime-db/db.json'), - use: path.join(__dirname, 'mime-db-shrinking-loader.js') - } - ], - generator: { - json: { - JSONParse: false +module.exports = (env) => { + /** @type {import('webpack').Configuration} */ + const config = { + name: 'main', + mode: process.env.NODE_ENV, + devtool: isDevMode ? 'eval-cheap-module-source-map' : false, + entry: { + main: path.join(__dirname, '../src/main/index.js'), + }, + module: { + rules: [ + { + test: /\.js$/, + use: 'babel-loader', + exclude: /node_modules/, + }, + { + resource: path.resolve(__dirname, '../node_modules/mime-db/db.json'), + use: path.join(__dirname, 'mime-db-shrinking-loader.js') + } + ], + generator: { + json: { + JSONParse: false + } } - } - }, - // webpack defaults to only optimising the production builds, so having this here is fine - optimization: { - minimizer: [ - '...', // extend webpack's list instead of overwriting it - new JsonMinimizerPlugin({ - exclude: /\/locales\/.*\.json/ + }, + // webpack defaults to only optimising the production builds, so having this here is fine + optimization: { + minimizer: [ + '...', // extend webpack's list instead of overwriting it + new JsonMinimizerPlugin({ + exclude: /\/locales\/.*\.json/ + }) + ] + }, + node: { + __dirname: isDevMode, + __filename: isDevMode + }, + plugins: [ + new webpack.DefinePlugin({ + 'process.platform': `'${process.platform}'`, + 'process.env.IS_ELECTRON_MAIN': true, + 'process.env.IS_TESTS': env.IS_TESTS }) - ] - }, - node: { - __dirname: isDevMode, - __filename: isDevMode - }, - plugins: [ - new webpack.DefinePlugin({ - 'process.platform': `'${process.platform}'`, - 'process.env.IS_ELECTRON_MAIN': true - }) - ], - output: { - filename: '[name].js', - libraryTarget: 'commonjs2', - path: path.join(__dirname, '../dist'), - }, - target: 'electron-main', -} + ], + output: { + filename: '[name].js', + libraryTarget: 'commonjs2', + path: path.join(__dirname, '../dist'), + }, + target: 'electron-main', + } -if (!isDevMode) { - config.plugins.push( - new CopyWebpackPlugin({ - patterns: [ - { - from: path.join(__dirname, '../static'), - to: path.join(__dirname, '../dist/static'), - globOptions: { - dot: true, - ignore: ['**/.*', '**/locales/**', '**/pwabuilder-sw.js', '**/manifest.json', '**/dashFiles/**', '**/storyboards/**'], + if (!isDevMode) { + config.plugins.push( + new CopyWebpackPlugin({ + patterns: [ + { + from: path.join(__dirname, '../static'), + to: path.join(__dirname, '../dist/static'), + globOptions: { + dot: true, + ignore: ['**/.*', '**/locales/**', '**/pwabuilder-sw.js', '**/manifest.json', '**/dashFiles/**', '**/storyboards/**'], + }, }, - }, - ] - }) - ) -} + ] + }) + ) + } -module.exports = config + return config +} diff --git a/eslint.config.mjs b/eslint.config.mjs index 840cc031ae9e2..ec86e1bed1cd7 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,3 +1,4 @@ +import { defineConfig } from "eslint/config"; import eslintPluginVue from 'eslint-plugin-vue' import vuejsAccessibility from 'eslint-plugin-vuejs-accessibility' import eslintPluginUnicorn from 'eslint-plugin-unicorn' @@ -12,13 +13,14 @@ import jsdoc from 'eslint-plugin-jsdoc' import stylistic from '@stylistic/eslint-plugin' import eslintPluginImportX from 'eslint-plugin-import-x' import eslintPluginN from 'eslint-plugin-n' +import eslintPluginPlaywright from 'eslint-plugin-playwright' import eslintPluginPromise from 'eslint-plugin-promise' import freetube from './_scripts/eslint-rules/plugin.mjs' import activeLocales from './static/locales/activeLocales.json' with { type: 'json' } -export default [ +export default defineConfig([ { ignores: [ 'build/', @@ -436,7 +438,7 @@ export default [ } }, { - files: ['_scripts/**/*.mjs'], + files: ['_scripts/**/*.mjs', 'tests/**/*.mjs'], languageOptions: { globals: globals.node, ecmaVersion: 'latest', @@ -456,5 +458,24 @@ export default [ 'unicorn/prefer-date-now': 'error', 'unicorn/prefer-array-index-of': 'error', } + }, + { + files: ['tests/playwright/helpers.mjs', 'tests/playwright/**/*spec.mjs'], + extends: [eslintPluginPlaywright.configs['flat/recommended']], + settings: { + playwright: { + globalAliases: { + test: ["electronDesktopTest", "electronMobileTest", "electronTabletTest"], + } + } + }, + rules: { + "playwright/expect-expect": [ + "error", + { + "assertFunctionNames": ["validateAccessibility"], + } + ] + } } -] +]) diff --git a/package.json b/package.json index fb0d667b4f615..d6236981d17d7 100644 --- a/package.json +++ b/package.json @@ -34,8 +34,8 @@ "lint-all": "run-p lint lint-json", "lint": "run-p eslint-lint lint-style", "lint-fix": "run-p eslint-lint-fix lint-style-fix", - "eslint-lint": "eslint --config eslint.config.mjs \"src/**/*.js\" \"src/renderer/**/*.vue\" \"static/*.js\" \"_scripts/*.js\" \"_scripts/**/*.mjs\"", - "eslint-lint-fix": "eslint --config eslint.config.mjs --fix \"src/**/*.js\" \"src/renderer/**/*.vue\" \"static/*.js\" \"_scripts/*.js\" \"_scripts/**/*.mjs\"", + "eslint-lint": "eslint --config eslint.config.mjs \"src/**/*.js\" \"src/renderer/**/*.vue\" \"static/*.js\" \"_scripts/*.js\" \"_scripts/**/*.mjs\" \"tests/**/*.mjs\"", + "eslint-lint-fix": "eslint --config eslint.config.mjs --fix \"src/**/*.js\" \"src/renderer/**/*.vue\" \"static/*.js\" \"_scripts/*.js\" \"_scripts/**/*.mjs\" \"tests/**/*.mjs\"", "lint-json": "eslint --config eslint.config.mjs \"static/**/*.json\"", "lint-style": "stylelint \"src/**/*.{css,scss}\"", "lint-style-fix": "stylelint --fix \"src/**/*.{css,scss}\"", @@ -44,10 +44,14 @@ "pack:main": "webpack --mode=production --config-node-env=production --config _scripts/webpack.main.config.js", "pack:renderer": "webpack --mode=production --config-node-env=production --config _scripts/webpack.renderer.config.js", "pack:preload": "webpack --mode=production --config-node-env=production --config _scripts/webpack.preload.config.js", + "pack:tests": "run-p pack:main-tests pack:renderer pack:preload pack:botGuardScript && node _scripts/injectAllowedPaths.mjs", + "pack:main-tests": "webpack --mode=production --config-node-env=production --env \"IS_TESTS=true\" --config _scripts/webpack.main.config.js", "pack:web": "webpack --mode=production --config-node-env=production --config _scripts/webpack.web.config.js", "pack:botGuardScript": "webpack --config _scripts/webpack.botGuardScript.config.js", "checkforbadtemplates": "node _scripts/findMissingTemplates.mjs", - "ci": "yarn install --silent --frozen-lockfile --network-concurrency 1" + "ci": "yarn install --silent --frozen-lockfile --network-concurrency 1", + "test:playwright": "run-s pack:tests test:playwright-nopack", + "test:playwright-nopack": "playwright test" }, "dependencies": { "@fortawesome/fontawesome-svg-core": "^7.2.0", @@ -73,11 +77,13 @@ "youtubei.js": "^17.0.1" }, "devDependencies": { + "@axe-core/playwright": "^4.11.1", "@babel/core": "^7.29.0", "@babel/preset-env": "^7.29.2", "@double-great/stylelint-a11y": "^3.4.9", "@eslint/js": "^10.0.1", "@intlify/eslint-plugin-vue-i18n": "^4.3.0", + "@playwright/test": "^1.58.2", "@stylistic/eslint-plugin": "^5.10.0", "babel-loader": "^10.1.1", "copy-webpack-plugin": "^14.0.0", @@ -90,6 +96,7 @@ "eslint-plugin-jsdoc": "^62.9.0", "eslint-plugin-jsonc": "^3.1.2", "eslint-plugin-n": "^17.24.0", + "eslint-plugin-playwright": "^2.5.1", "eslint-plugin-promise": "^7.2.1", "eslint-plugin-unicorn": "^64.0.0", "eslint-plugin-vue": "^10.8.0", diff --git a/playwright.config.mjs b/playwright.config.mjs new file mode 100644 index 0000000000000..bd5726ebefa8c --- /dev/null +++ b/playwright.config.mjs @@ -0,0 +1,7 @@ +/** @type {import('@playwright/test').PlaywrightTestConfig} */ +export default { + testDir: './tests/playwright', + testMatch: '*.spec.mjs', + workers: 1, + fullyParallel: false +} diff --git a/src/datastores/index.js b/src/datastores/index.js index df5959eb1ea22..e98e1ef0a5d40 100644 --- a/src/datastores/index.js +++ b/src/datastores/index.js @@ -29,6 +29,7 @@ function createDatastore(name) { return new Datastore({ filename: dbPath(name), autoload: !process.env.IS_ELECTRON_MAIN, + inMemoryOnly: !!process.env.IS_TESTS, // Automatically clean up corrupted data, instead of crashing corruptAlertThreshold: 1 }) diff --git a/src/renderer/components/TopNav/TopNav.vue b/src/renderer/components/TopNav/TopNav.vue index 252a54089eda1..706339d338a6e 100644 --- a/src/renderer/components/TopNav/TopNav.vue +++ b/src/renderer/components/TopNav/TopNav.vue @@ -46,6 +46,8 @@