diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 13b787ef46..5ec917bbfe 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -1,47 +1,49 @@ -name: E2E tests +name: E2E Tests + on: pull_request: push: branches: - main + jobs: - test: - timeout-minutes: 10 + e2e: runs-on: ubuntu-latest - strategy: - matrix: - shard: [1/3, 2/3, 3/3] + steps: - - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4 + - uses: actions/checkout@v4 - - run: corepack enable + - name: Enable corepack + run: corepack enable - - uses: actions/setup-node@v3 + - name: Setup Node.js + uses: actions/setup-node@v3 with: node-version: 20 - cache: 'pnpm' - - - name: Get Playwright version - id: playwright-version - run: echo "PLAYWRIGHT_VERSION=$(jq -r .dependencies.playwright package.json)" >> "$GITHUB_OUTPUT" + cache: pnpm - name: Install dependencies run: pnpm install - - name: Build app + - name: Install system dependencies for Playwright + run: | + sudo apt-get update + sudo apt-get install -y \ + libasound2t64 \ + libffi8 \ + libx264-164 \ + libnss3 \ + libatk-bridge2.0-0 \ + libxkbcommon0 \ + libgbm1 \ + libgtk-3-0 \ + libdbus-glib-1-2 || true + + - name: Build app for E2E run: pnpm build - - name: Restore Playwright browsers from cache - uses: actions/cache@v3 - with: - path: ~/.cache/ms-playwright - key: ${{ runner.os }}-playwright-${{ steps.playwright-version.outputs.PLAYWRIGHT_VERSION }}-${{ hashFiles('**/playwright.config.ts') }} - restore-keys: | - ${{ runner.os }}-playwright-${{ steps.playwright-version.outputs.PLAYWRIGHT_VERSION }}- - ${{ runner.os }}-playwright- - - name: Install Playwright Browsers - run: pnpm exec playwright install --with-deps + run: pnpm exec playwright install - name: Run Playwright tests - run: pnpm run test:e2e --shard=${{ matrix.shard }} + run: pnpm exec playwright test diff --git a/components.d.ts b/components.d.ts index 3e65c3cc52..4bfffdba25 100644 --- a/components.d.ts +++ b/components.d.ts @@ -107,6 +107,7 @@ declare module '@vue/runtime-core' { Ipv4RangeExpander: typeof import('./src/tools/ipv4-range-expander/ipv4-range-expander.vue')['default'] Ipv4SubnetCalculator: typeof import('./src/tools/ipv4-subnet-calculator/ipv4-subnet-calculator.vue')['default'] Ipv6UlaGenerator: typeof import('./src/tools/ipv6-ula-generator/ipv6-ula-generator.vue')['default'] + JavascriptObfuscator: typeof import('./src/tools/javascript-obfuscator/javascript-obfuscator.vue')['default'] JsonDiff: typeof import('./src/tools/json-diff/json-diff.vue')['default'] JsonMinify: typeof import('./src/tools/json-minify/json-minify.vue')['default'] JsonToCsv: typeof import('./src/tools/json-to-csv/json-to-csv.vue')['default'] diff --git a/playwright.config.ts b/playwright.config.ts index 5257c526d3..a50f38688e 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -10,30 +10,19 @@ const useWebServer = process.env.NO_WEB_SERVER !== 'true'; export default defineConfig({ testDir: './src', testMatch: /\.e2e\.(spec\.)?ts$/, - /* Run tests in files in parallel */ fullyParallel: true, - /* Fail the build on CI if you accidentally left test.only in the source code. */ forbidOnly: isCI, - /* Retry on CI only */ retries: isCI ? 2 : 0, - /* Opt out of parallel tests on CI. */ workers: isCI ? 1 : undefined, - /* Reporter to use. See https://playwright.dev/docs/test-reporters */ reporter: 'html', - /* 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: baseUrl, - - /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: 'on-first-retry', - testIdAttribute: 'data-test-id', locale: 'en-GB', timezoneId: 'Europe/Paris', }, - /* Configure projects for major browsers */ projects: [ { name: 'chromium', @@ -42,24 +31,25 @@ export default defineConfig({ { name: 'firefox', - use: { ...devices['Desktop Firefox'] }, - }, - - { - name: 'webkit', - use: { ...devices['Desktop Safari'] }, + use: { + ...devices['Desktop Firefox'], + launchOptions: { + firefoxUserPrefs: { + 'gfx.webrender.force-disabled': true, + 'gfx.x11-egl.force-disabled': true, + 'media.hardware-video-decoding.enabled': false, + 'layers.acceleration.disabled': true, + }, + }, + }, }, ], - /* Run your local dev server before starting the tests */ - - ...(useWebServer - && { - webServer: { - command: 'npm run preview', - url: 'http://localhost:5050', - reuseExistingServer: !isCI, - }, - } - ), + ...(useWebServer && { + webServer: { + command: 'npm run preview', + url: 'http://localhost:5050', + reuseExistingServer: !isCI, + }, + }), }); diff --git a/src/tools/index.ts b/src/tools/index.ts index 388cfaf494..0998aa06a5 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -1,6 +1,7 @@ import { tool as base64FileConverter } from './base64-file-converter'; import { tool as base64StringConverter } from './base64-string-converter'; import { tool as basicAuthGenerator } from './basic-auth-generator'; +import { tool as javascriptObfuscator } from './javascript-obfuscator'; import { tool as emailNormalizer } from './email-normalizer'; import { tool as asciiTextDrawer } from './ascii-text-drawer'; @@ -160,6 +161,7 @@ export const toolsByCategory: ToolCategory[] = [ emailNormalizer, regexTester, regexMemo, + javascriptObfuscator, ], }, { diff --git a/src/tools/javascript-obfuscator/index.ts b/src/tools/javascript-obfuscator/index.ts new file mode 100644 index 0000000000..e97c698cb1 --- /dev/null +++ b/src/tools/javascript-obfuscator/index.ts @@ -0,0 +1,12 @@ +import { Lock } from '@vicons/tabler'; +import { defineTool } from '../tool'; + +export const tool = defineTool({ + name: 'JavaScript Obfuscator', + path: '/javascript-obfuscator', + description: 'Javascript code obfuscator using base64 or rot13 encoding by Forza for IT Tools', + keywords: ['javascript', 'obfuscator'], + component: () => import('./javascript-obfuscator.vue'), + icon: Lock, + createdAt: new Date('2025-12-31'), +}); diff --git a/src/tools/javascript-obfuscator/javascript-obfuscator.e2e.spec.ts b/src/tools/javascript-obfuscator/javascript-obfuscator.e2e.spec.ts new file mode 100644 index 0000000000..ee4e307df9 --- /dev/null +++ b/src/tools/javascript-obfuscator/javascript-obfuscator.e2e.spec.ts @@ -0,0 +1,11 @@ +import { expect, test } from '@playwright/test'; + +test.describe('Tool - JavaScript Obfuscator', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/javascript-obfuscator'); + }); + + test('Has correct title', async ({ page }) => { + await expect(page).toHaveTitle('JavaScript Obfuscator - IT Tools'); + }); +}); diff --git a/src/tools/javascript-obfuscator/javascript-obfuscator.service.test.ts b/src/tools/javascript-obfuscator/javascript-obfuscator.service.test.ts new file mode 100644 index 0000000000..5190fe1767 --- /dev/null +++ b/src/tools/javascript-obfuscator/javascript-obfuscator.service.test.ts @@ -0,0 +1,28 @@ +import { Buffer } from 'node:buffer'; +import { describe, expect, it } from 'vitest'; +import { obfuscateJavascript } from './javascript-obfuscator.service'; + +describe('JavascriptObfuscator', () => { + it('base64 encodes and wraps with atob', () => { + const src = 'console.log(\'x\');'; + const out = obfuscateJavascript(src, 'base64'); + + expect(out).toContain('atob(\''); + const m = out.match(/atob\('(.+)'\)/); + expect(m).toBeTruthy(); + if (m) { + const b64 = m[1]; + const decoded = decodeURIComponent(escape(Buffer.from(b64, 'base64').toString('binary'))); + expect(decoded).toBe(src); + } + }); + + it('rot13 encodes and includes runtime decoder', () => { + const src = 'console.log(\'a\');'; + const out = obfuscateJavascript(src, 'rot13'); + + expect(out).toContain('eval(r(s))'); + const r = src.replace(/[A-Za-z]/g, c => String.fromCharCode((c <= 'Z' ? 65 : 97) + ((c.charCodeAt(0) - (c <= 'Z' ? 65 : 97) + 13) % 26))); + expect(out).toContain(r); + }); +}); diff --git a/src/tools/javascript-obfuscator/javascript-obfuscator.service.ts b/src/tools/javascript-obfuscator/javascript-obfuscator.service.ts new file mode 100644 index 0000000000..073c035418 --- /dev/null +++ b/src/tools/javascript-obfuscator/javascript-obfuscator.service.ts @@ -0,0 +1,31 @@ +import { get } from '@vueuse/core'; +import { type MaybeRef, computed } from 'vue'; + +export { obfuscateJavascript, useObfuscateJavascript }; + +function base64Encode(str: string) { + // encode UTF-8 to base64 + return btoa(unescape(encodeURIComponent(str))); +} + +function rot13Encode(str: string) { + return str.replace(/[A-Za-z]/g, c => String.fromCharCode((c <= 'Z' ? 65 : 97) + ((c.charCodeAt(0) - (c <= 'Z' ? 65 : 97) + 13) % 26))); +} + +function obfuscateJavascript(code: string, method: 'base64' | 'rot13' = 'base64'): string { + if (!code) { + return ''; + } + + if (method === 'base64') { + const b = base64Encode(code); + return `(function(){eval(atob('${b}'));})();`; + } + + const r = rot13Encode(code); + return `(function(s){function r(u){return u.replace(/[A-Za-z]/g,c=>String.fromCharCode((c<='Z'?65:97)+((c.charCodeAt(0)-(c<='Z'?65:97)+13)%26)))};eval(r(s))})('${r}');`; +} + +function useObfuscateJavascript(code: MaybeRef, method?: MaybeRef<'base64' | 'rot13'>) { + return computed(() => obfuscateJavascript(get(code), get(method) || 'base64')); +} diff --git a/src/tools/javascript-obfuscator/javascript-obfuscator.vue b/src/tools/javascript-obfuscator/javascript-obfuscator.vue new file mode 100644 index 0000000000..ce9492e6b1 --- /dev/null +++ b/src/tools/javascript-obfuscator/javascript-obfuscator.vue @@ -0,0 +1,555 @@ + + + + +