diff --git a/.changeset/wet-sheep-call.md b/.changeset/wet-sheep-call.md new file mode 100644 index 00000000000..b001ea4cddc --- /dev/null +++ b/.changeset/wet-sheep-call.md @@ -0,0 +1,5 @@ +--- +"create-fuels": patch +--- + +feat: add UI tests to `create fuels` template diff --git a/apps/create-fuels-counter-guide/.gitignore b/apps/create-fuels-counter-guide/.gitignore index aff61832060..7ea0ef9b6b2 100644 --- a/apps/create-fuels-counter-guide/.gitignore +++ b/apps/create-fuels-counter-guide/.gitignore @@ -31,4 +31,7 @@ src/sway-api/scripts src/sway-api/index.ts sway-programs/**/out -.turbo \ No newline at end of file +.turbo + +playwright-report +test-results \ No newline at end of file diff --git a/apps/create-fuels-counter-guide/package.json b/apps/create-fuels-counter-guide/package.json index b8c73d751ec..42643ee96f0 100644 --- a/apps/create-fuels-counter-guide/package.json +++ b/apps/create-fuels-counter-guide/package.json @@ -4,7 +4,7 @@ "version": "0.1.0", "type": "module", "scripts": { - "test": "vitest", + "test:ui": "./test/ui/test-ui.sh", "original:dev": "vite", "original:build": "tsc -b && vite build", "original:start": "next start", @@ -18,10 +18,10 @@ "@fuels/react": "^0.27.1", "@tanstack/react-query": "^5.52.1", "@tanstack/react-router": "^1.48.1", - "fuels": "workspace:*", "@wagmi/connectors": "^5.1.8", "@wagmi/core": "^2.13.4", "dotenv": "^16.4.5", + "fuels": "workspace:*", "react": "^18.3.1", "react-dom": "^18.3.1", "react-hot-toast": "^2.4.1", @@ -29,6 +29,7 @@ }, "devDependencies": { "@eslint/js": "^9.9.1", + "@playwright/test": "^1.46.1", "@tanstack/router-plugin": "^1.47.0", "@types/node": "^22.2.0", "@types/react": "^18.3.5", diff --git a/apps/create-fuels-counter-guide/playwright.config.ts b/apps/create-fuels-counter-guide/playwright.config.ts new file mode 100644 index 00000000000..f085454c869 --- /dev/null +++ b/apps/create-fuels-counter-guide/playwright.config.ts @@ -0,0 +1,42 @@ +import { defineConfig, devices } from '@playwright/test'; + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './test/ui', + /* Run tests in files in parallel */ + fullyParallel: true, + /* 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: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: [['html', { open: 'never' }]], + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + timeout: 60000, + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], + webServer: [ + { + command: 'pnpm run original:dev', + port: 5173, + reuseExistingServer: !process.env.CI, + }, + { + command: 'pnpm run fuels:dev', + port: 4000, + reuseExistingServer: !process.env.CI, + } + ] +}); diff --git a/apps/create-fuels-counter-guide/test/ui/test-ui.sh b/apps/create-fuels-counter-guide/test/ui/test-ui.sh new file mode 100755 index 00000000000..ee7f075a669 --- /dev/null +++ b/apps/create-fuels-counter-guide/test/ui/test-ui.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +# This script installs the necessary dependencies for the UI tests and runs them. + +pnpm exec playwright install --with-deps > /dev/null 2>&1 +pnpm exec playwright test +TEST_RESULT=$? + +exit $TEST_RESULT \ No newline at end of file diff --git a/apps/create-fuels-counter-guide/test/ui/ui.test.ts b/apps/create-fuels-counter-guide/test/ui/ui.test.ts new file mode 100644 index 00000000000..4e31bad85b7 --- /dev/null +++ b/apps/create-fuels-counter-guide/test/ui/ui.test.ts @@ -0,0 +1,54 @@ +import { test, expect } from '@playwright/test'; + +const WEB_SERVER_URL = 'http://localhost:5173'; + +test.extend({ + page: async ({ page }, use) => { + await page.evaluate(() => window.localStorage.clear()); + await use(page); + }, +}); + +test('counter contract - increment function call works properly', async ({ page }) => { + await page.goto(WEB_SERVER_URL, { waitUntil: 'networkidle' }); + + await page.waitForTimeout(2000); + + const topUpWalletButton = page.getByText('Top-up Wallet'); + await topUpWalletButton.click(); + + await page.waitForTimeout(2000); + + const initialCounterValue = +(page.getByTestId('counter').textContent); + + const incrementButton = page.getByText('Increment Counter'); + await incrementButton.click(); + + await page.waitForTimeout(2000); + + const counterValueAfterIncrement = +(page.getByTestId('counter').textContent); + expect(+(counterValueAfterIncrement)).toEqual(+initialCounterValue + 1); +}); + +// #region decrement-counter-ui-test +test('counter contract - decrement function call works properly', async ({ page }) => { + await page.goto(WEB_SERVER_URL, { waitUntil: 'networkidle' }); + + await page.waitForTimeout(2000); // These timeouts are needed to ensure that we wait for transactions to be mined + + const topUpWalletButton = page.getByText('Top-up Wallet'); + await topUpWalletButton.click(); + + await page.waitForTimeout(2000); + + const initialCounterValue = +(page.getByTestId('counter').textContent); + + const decrementButton = page.getByText('Decrement Counter'); + await decrementButton.click(); + + await page.waitForTimeout(2000); + + const counterValueAfterDecrement = +(page.getByTestId('counter').textContent); + expect(+(counterValueAfterDecrement)).toEqual(+initialCounterValue - 1); +}); +// #endregion decrement-counter-ui-test \ No newline at end of file diff --git a/apps/docs/src/guide/creating-a-fuel-dapp/index.md b/apps/docs/src/guide/creating-a-fuel-dapp/index.md index 809c88fa564..a6c344a0acc 100644 --- a/apps/docs/src/guide/creating-a-fuel-dapp/index.md +++ b/apps/docs/src/guide/creating-a-fuel-dapp/index.md @@ -199,6 +199,10 @@ We've provided some examples for each program type in the `./test` directory of <<< @/../../docs-snippets/src/guide/create-fuels/decrement_counter.test.ts#decrement-counter{ts:line-numbers} +The template also comes with a UI testing setup using [Playwright](https://playwright.dev/). We can add a test for our new `decrement_counter` function in the `./test/ui/ui.test.ts` file: + +<<< @/../../create-fuels-counter-guide/test/ui/ui.test.ts#decrement-counter-ui-test{ts:line-numbers} + ## Next Steps - Now that you have a basic counter dApp running and have the `npm create fuels` workflow powering you, you can start building more complex dApps using the Fuel Stack. A good place to start for ideas and reference code is the [Sway Applications Repo](https://github.com/FuelLabs/sway-applications). diff --git a/packages/create-fuels/src/lib/rewriteTemplateFiles.test.ts b/packages/create-fuels/src/lib/rewriteTemplateFiles.test.ts index af63079911e..c6c4daa09cd 100644 --- a/packages/create-fuels/src/lib/rewriteTemplateFiles.test.ts +++ b/packages/create-fuels/src/lib/rewriteTemplateFiles.test.ts @@ -65,7 +65,7 @@ describe('rewriteTemplateFiles', () => { }); it('should rewrite the test files', () => { - const testDir = join(paths.templateRoot, 'test'); + const testDir = join(paths.templateRoot, 'test', 'integration'); const programs = [ { program: 'contract', diff --git a/packages/create-fuels/src/lib/rewriteTemplateFiles.ts b/packages/create-fuels/src/lib/rewriteTemplateFiles.ts index 86a487486bd..83c51c4a7a5 100644 --- a/packages/create-fuels/src/lib/rewriteTemplateFiles.ts +++ b/packages/create-fuels/src/lib/rewriteTemplateFiles.ts @@ -19,7 +19,7 @@ export const rewriteTemplateFiles = (templateDir: string) => { writeFileSync(fuelsConfigFilePath, contents); // tests - const testDir = join(templateDir, 'test'); + const testDir = join(templateDir, 'test', 'integration'); const programs = ['contract', 'predicate', 'script']; programs.forEach((program) => { const testFilePath = join(testDir, `${program}.test.ts`); diff --git a/playwright-tests/create-fuels.test.ts b/playwright-tests/create-fuels.test.ts index f2584e5f33e..e5a8de13590 100644 --- a/playwright-tests/create-fuels.test.ts +++ b/playwright-tests/create-fuels.test.ts @@ -13,23 +13,22 @@ test.extend({ test('counter contract - increment function call works properly', async ({ page }) => { await page.goto(WEB_SERVER_URL, { waitUntil: 'networkidle' }); - await page.waitForTimeout(5000); + await page.waitForTimeout(2000); const topUpWalletButton = page.getByText('Top-up Wallet'); await topUpWalletButton.click(); - const welcomeToFuelText = page.getByText('Welcome to Fuel'); - await expect(welcomeToFuelText).toBeVisible(); + await page.waitForTimeout(2000); - await page.waitForTimeout(4000); + const initialCounterValue = +page.getByTestId('counter').textContent; const incrementButton = page.getByText('Increment Counter'); await incrementButton.click(); await page.waitForTimeout(2000); - const counter = page.getByTestId('counter'); - await expect(counter).toBeVisible(); + const counterValueAfterIncrement = +page.getByTestId('counter').textContent; + expect(counterValueAfterIncrement).toEqual(initialCounterValue + 1); }); test('top-up wallet button', async ({ page }) => { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c48785652dc..e6397d4f754 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -94,7 +94,7 @@ importers: version: 3.2.0(eslint@8.57.0) eslint-plugin-import: specifier: ^2.29.1 - version: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) + version: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) eslint-plugin-jsdoc: specifier: ^46.8.2 version: 46.8.2(eslint@8.57.0) @@ -225,6 +225,9 @@ importers: '@eslint/js': specifier: ^9.9.1 version: 9.9.1 + '@playwright/test': + specifier: ^1.46.1 + version: 1.46.1 '@tanstack/router-plugin': specifier: ^1.47.0 version: 1.48.6(vite@5.4.2(@types/node@22.5.0)(terser@5.31.6)) @@ -1271,6 +1274,9 @@ importers: specifier: ^17.5.1 version: 17.5.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) devDependencies: + '@playwright/test': + specifier: ^1.46.1 + version: 1.46.1 '@types/node': specifier: ^22.2.0 version: 22.2.0 @@ -1344,6 +1350,9 @@ importers: '@eslint/js': specifier: ^9.9.1 version: 9.9.1 + '@playwright/test': + specifier: ^1.46.1 + version: 1.46.1 '@tanstack/router-plugin': specifier: ^1.47.0 version: 1.48.6(vite@5.4.2(@types/node@22.5.0)(terser@5.31.6)) @@ -25601,7 +25610,7 @@ snapshots: dependencies: confusing-browser-globals: 1.0.11 eslint: 8.57.0 - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) object.assign: 4.1.4 object.entries: 1.1.6 semver: 6.3.0 @@ -25612,7 +25621,7 @@ snapshots: '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.4.5) eslint: 8.57.0 eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0))(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) eslint-config-next@14.2.7(eslint@8.57.0)(typescript@5.4.5): dependencies: @@ -25622,7 +25631,7 @@ snapshots: eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) eslint-plugin-jsx-a11y: 6.9.0(eslint@8.57.0) eslint-plugin-react: 7.35.0(eslint@8.57.0) eslint-plugin-react-hooks: 4.6.2(eslint@8.57.0) @@ -25671,31 +25680,13 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0))(eslint@8.57.0): - dependencies: - debug: 4.3.6 - enhanced-resolve: 5.17.1 - eslint: 8.57.0 - eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0) - fast-glob: 3.3.2 - get-tsconfig: 4.7.6 - is-core-module: 2.15.0 - is-glob: 4.0.3 - transitivePeerDependencies: - - '@typescript-eslint/parser' - - eslint-import-resolver-node - - eslint-import-resolver-webpack - - supports-color - optional: true - eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0): dependencies: debug: 4.3.6 enhanced-resolve: 5.17.1 eslint: 8.57.0 eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) fast-glob: 3.3.2 get-tsconfig: 4.7.6 is-core-module: 2.15.0 @@ -25716,17 +25707,6 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.8.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0): - dependencies: - debug: 3.2.7 - optionalDependencies: - '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.4.5) - eslint: 8.57.0 - eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0))(eslint@8.57.0) - transitivePeerDependencies: - - supports-color - eslint-module-utils@2.8.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0): dependencies: debug: 3.2.7 @@ -25775,7 +25755,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0): + eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0): dependencies: array-includes: 3.1.8 array.prototype.findlastindex: 1.2.3 @@ -25785,34 +25765,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) - hasown: 2.0.2 - is-core-module: 2.15.0 - is-glob: 4.0.3 - minimatch: 3.1.2 - object.fromentries: 2.0.8 - object.groupby: 1.0.1 - object.values: 1.2.0 - semver: 6.3.1 - tsconfig-paths: 3.15.0 - optionalDependencies: - '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.4.5) - transitivePeerDependencies: - - eslint-import-resolver-typescript - - eslint-import-resolver-webpack - - supports-color - - eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0): - dependencies: - array-includes: 3.1.8 - array.prototype.findlastindex: 1.2.3 - array.prototype.flat: 1.3.2 - array.prototype.flatmap: 1.3.2 - debug: 3.2.7 - doctrine: 2.1.0 - eslint: 8.57.0 - eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) + eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0) hasown: 2.0.2 is-core-module: 2.15.0 is-glob: 4.0.3 diff --git a/scripts/tests-validate.sh b/scripts/tests-validate.sh index bb665beba17..86070004cf2 100755 --- a/scripts/tests-validate.sh +++ b/scripts/tests-validate.sh @@ -2,7 +2,9 @@ ROOT=$(cd "$(dirname "$0")/.."; pwd) -FILES=$(find $ROOT/{apps,packages,internal} -name '*.test.ts') +# ignore files in apps/create-fuels-counter-guide/test/ui +FILES=$(find $ROOT/{apps,packages,internal} -name '*.test.ts' | grep -v "apps/create-fuels-counter-guide/test/ui") + INVALID_FILES=$(grep -LE "@group\s+(node|browser|e2e|integration)" $FILES) if [ ! -z "$INVALID_FILES" ]; then diff --git a/templates/nextjs/.gitignore b/templates/nextjs/.gitignore index 2bf8d0420be..993cdb130df 100644 --- a/templates/nextjs/.gitignore +++ b/templates/nextjs/.gitignore @@ -40,4 +40,7 @@ src/sway-api/scripts src/sway-api/index.ts sway-programs/**/out -.turbo \ No newline at end of file +.turbo + +test-results +playwright-report \ No newline at end of file diff --git a/templates/nextjs/gitignore b/templates/nextjs/gitignore index 9e82727fa10..1be56e10fab 100644 --- a/templates/nextjs/gitignore +++ b/templates/nextjs/gitignore @@ -41,4 +41,7 @@ src/sway-api/scripts src/sway-api/index.ts sway-programs/**/out -.turbo \ No newline at end of file +.turbo + +test-results +playwright-report \ No newline at end of file diff --git a/templates/nextjs/package.json b/templates/nextjs/package.json index fe929a30138..a4a012bb84a 100644 --- a/templates/nextjs/package.json +++ b/templates/nextjs/package.json @@ -9,7 +9,8 @@ "build": "pnpm run xprebuild && next build", "start": "next start", "lint": "next lint", - "test": "vitest" + "test": "vitest", + "test:ui": "./test/ui/test-ui.sh" }, "dependencies": { "@fuels/connectors": "^0.27.1", @@ -26,6 +27,7 @@ "react-use": "^17.5.1" }, "devDependencies": { + "@playwright/test": "^1.46.1", "@types/node": "^22.2.0", "@types/react": "^18.3.5", "@types/react-dom": "^18.3", diff --git a/templates/nextjs/playwright.config.ts b/templates/nextjs/playwright.config.ts new file mode 100644 index 00000000000..130a994ff2b --- /dev/null +++ b/templates/nextjs/playwright.config.ts @@ -0,0 +1,42 @@ +import { defineConfig, devices } from '@playwright/test'; + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './test/ui', + /* Run tests in files in parallel */ + fullyParallel: true, + /* 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: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: [['html', { open: 'never' }]], + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + timeout: 60000, + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], + webServer: [ + { + command: 'pnpm run dev', + port: 3000, + reuseExistingServer: !process.env.CI, + }, + { + command: 'pnpm run fuels:dev', + port: 4000, + reuseExistingServer: !process.env.CI, + }, + ], +}); diff --git a/templates/nextjs/test/contract.test.ts b/templates/nextjs/test/integration/contract.test.ts similarity index 97% rename from templates/nextjs/test/contract.test.ts rename to templates/nextjs/test/integration/contract.test.ts index 229e6240839..17fb3d1d66c 100644 --- a/templates/nextjs/test/contract.test.ts +++ b/templates/nextjs/test/integration/contract.test.ts @@ -7,7 +7,7 @@ import { describe, test, expect } from 'vitest'; * * Can't find these imports? Make sure you've run `fuels build` to generate these with typegen. */ -import { TestContractFactory } from '../src/sway-api'; +import { TestContractFactory } from '../../src/sway-api'; /** * @group node diff --git a/templates/vite/test/predicate.test.ts b/templates/nextjs/test/integration/predicate.test.ts similarity index 96% rename from templates/vite/test/predicate.test.ts rename to templates/nextjs/test/integration/predicate.test.ts index be9f8845b20..17a9b6f9540 100644 --- a/templates/vite/test/predicate.test.ts +++ b/templates/nextjs/test/integration/predicate.test.ts @@ -7,7 +7,7 @@ import { describe, test, expect } from 'vitest'; * * Can't find these imports? Make sure you've run `fuels build` to generate these with typegen. */ -import { TestPredicate, TestPredicateInputs } from '../src/sway-api/predicates/TestPredicate'; +import { TestPredicate, TestPredicateInputs } from '../../src/sway-api/predicates/TestPredicate'; /** * @group node diff --git a/templates/nextjs/test/script.test.ts b/templates/nextjs/test/integration/script.test.ts similarity index 96% rename from templates/nextjs/test/script.test.ts rename to templates/nextjs/test/integration/script.test.ts index 729a51a60f5..af18de6ed12 100644 --- a/templates/nextjs/test/script.test.ts +++ b/templates/nextjs/test/integration/script.test.ts @@ -7,7 +7,7 @@ import { describe, test, expect } from 'vitest'; * * Can't find these imports? Make sure you've run `fuels build` to generate these with typegen. */ -import { TestScript } from '../src/sway-api'; +import { TestScript } from '../../src/sway-api'; /** * @group node diff --git a/templates/nextjs/test/ui/test-ui.sh b/templates/nextjs/test/ui/test-ui.sh new file mode 100644 index 00000000000..ee7f075a669 --- /dev/null +++ b/templates/nextjs/test/ui/test-ui.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +# This script installs the necessary dependencies for the UI tests and runs them. + +pnpm exec playwright install --with-deps > /dev/null 2>&1 +pnpm exec playwright test +TEST_RESULT=$? + +exit $TEST_RESULT \ No newline at end of file diff --git a/templates/nextjs/test/ui/ui.test.ts b/templates/nextjs/test/ui/ui.test.ts new file mode 100644 index 00000000000..aa0da696534 --- /dev/null +++ b/templates/nextjs/test/ui/ui.test.ts @@ -0,0 +1,31 @@ +import { test, expect } from '@playwright/test'; + +const WEB_SERVER_URL = 'http://localhost:3000'; + +test.extend({ + page: async ({ page }, use) => { + await page.evaluate(() => window.localStorage.clear()); + await use(page); + }, +}); + +test('counter contract - increment function call works properly', async ({ page }) => { + await page.goto(WEB_SERVER_URL, { waitUntil: 'networkidle' }); + + await page.waitForTimeout(2000); + + const topUpWalletButton = page.getByText('Top-up Wallet'); + await topUpWalletButton.click(); + + await page.waitForTimeout(2000); + + const initialCounterValue = +page.getByTestId('counter').textContent; + + const incrementButton = page.getByText('Increment Counter'); + await incrementButton.click(); + + await page.waitForTimeout(2000); + + const counterValueAfterIncrement = +page.getByTestId('counter').textContent; + expect(counterValueAfterIncrement).toEqual(initialCounterValue + 1); +}); diff --git a/templates/nextjs/vitest.config.mts b/templates/nextjs/vitest.config.mts index a6ca0394a34..92dda13d6fc 100644 --- a/templates/nextjs/vitest.config.mts +++ b/templates/nextjs/vitest.config.mts @@ -2,4 +2,7 @@ import { defineConfig } from "vitest/config"; export default defineConfig({ esbuild: { target: "es2022" }, + test: { + exclude: ["**/test/ui/**"], + }, }); diff --git a/templates/vite/.gitignore b/templates/vite/.gitignore index aff61832060..e88e6b4af18 100644 --- a/templates/vite/.gitignore +++ b/templates/vite/.gitignore @@ -31,4 +31,7 @@ src/sway-api/scripts src/sway-api/index.ts sway-programs/**/out -.turbo \ No newline at end of file +.turbo + +test-results +playwright-report \ No newline at end of file diff --git a/templates/vite/gitignore b/templates/vite/gitignore index aff61832060..e88e6b4af18 100644 --- a/templates/vite/gitignore +++ b/templates/vite/gitignore @@ -31,4 +31,7 @@ src/sway-api/scripts src/sway-api/index.ts sway-programs/**/out -.turbo \ No newline at end of file +.turbo + +test-results +playwright-report \ No newline at end of file diff --git a/templates/vite/package.json b/templates/vite/package.json index 892b22d5859..3e715be144a 100644 --- a/templates/vite/package.json +++ b/templates/vite/package.json @@ -9,7 +9,8 @@ "build": "pnpm run xprebuild && tsc -b && vite build", "lint": "eslint .", "fuels:dev": "fuels dev", - "test": "vitest" + "test": "vitest", + "test:ui": "./test/ui/test-ui.sh" }, "dependencies": { "@fuels/connectors": "^0.27.1", @@ -27,6 +28,7 @@ }, "devDependencies": { "@eslint/js": "^9.9.1", + "@playwright/test": "^1.46.1", "@tanstack/router-plugin": "^1.47.0", "@types/react": "^18.3.5", "@types/react-dom": "^18.3", diff --git a/templates/vite/playwright.config.ts b/templates/vite/playwright.config.ts new file mode 100644 index 00000000000..601e6f627b9 --- /dev/null +++ b/templates/vite/playwright.config.ts @@ -0,0 +1,42 @@ +import { defineConfig, devices } from '@playwright/test'; + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './test/ui', + /* Run tests in files in parallel */ + fullyParallel: true, + /* 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: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: [['html', { open: 'never' }]], + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + timeout: 60000, + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], + webServer: [ + { + command: 'pnpm run dev', + port: 5173, + reuseExistingServer: !process.env.CI, + }, + { + command: 'pnpm run fuels:dev', + port: 4000, + reuseExistingServer: !process.env.CI, + }, + ], +}); diff --git a/templates/vite/test/contract.test.ts b/templates/vite/test/integration/contract.test.ts similarity index 97% rename from templates/vite/test/contract.test.ts rename to templates/vite/test/integration/contract.test.ts index 229e6240839..17fb3d1d66c 100644 --- a/templates/vite/test/contract.test.ts +++ b/templates/vite/test/integration/contract.test.ts @@ -7,7 +7,7 @@ import { describe, test, expect } from 'vitest'; * * Can't find these imports? Make sure you've run `fuels build` to generate these with typegen. */ -import { TestContractFactory } from '../src/sway-api'; +import { TestContractFactory } from '../../src/sway-api'; /** * @group node diff --git a/templates/nextjs/test/predicate.test.ts b/templates/vite/test/integration/predicate.test.ts similarity index 96% rename from templates/nextjs/test/predicate.test.ts rename to templates/vite/test/integration/predicate.test.ts index be9f8845b20..17a9b6f9540 100644 --- a/templates/nextjs/test/predicate.test.ts +++ b/templates/vite/test/integration/predicate.test.ts @@ -7,7 +7,7 @@ import { describe, test, expect } from 'vitest'; * * Can't find these imports? Make sure you've run `fuels build` to generate these with typegen. */ -import { TestPredicate, TestPredicateInputs } from '../src/sway-api/predicates/TestPredicate'; +import { TestPredicate, TestPredicateInputs } from '../../src/sway-api/predicates/TestPredicate'; /** * @group node diff --git a/templates/vite/test/script.test.ts b/templates/vite/test/integration/script.test.ts similarity index 96% rename from templates/vite/test/script.test.ts rename to templates/vite/test/integration/script.test.ts index 729a51a60f5..af18de6ed12 100644 --- a/templates/vite/test/script.test.ts +++ b/templates/vite/test/integration/script.test.ts @@ -7,7 +7,7 @@ import { describe, test, expect } from 'vitest'; * * Can't find these imports? Make sure you've run `fuels build` to generate these with typegen. */ -import { TestScript } from '../src/sway-api'; +import { TestScript } from '../../src/sway-api'; /** * @group node diff --git a/templates/vite/test/ui/test-ui.sh b/templates/vite/test/ui/test-ui.sh new file mode 100755 index 00000000000..ee7f075a669 --- /dev/null +++ b/templates/vite/test/ui/test-ui.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +# This script installs the necessary dependencies for the UI tests and runs them. + +pnpm exec playwright install --with-deps > /dev/null 2>&1 +pnpm exec playwright test +TEST_RESULT=$? + +exit $TEST_RESULT \ No newline at end of file diff --git a/templates/vite/test/ui/ui.test.ts b/templates/vite/test/ui/ui.test.ts new file mode 100644 index 00000000000..b2fe5556a52 --- /dev/null +++ b/templates/vite/test/ui/ui.test.ts @@ -0,0 +1,31 @@ +import { test, expect } from '@playwright/test'; + +const WEB_SERVER_URL = 'http://localhost:5173'; + +test.extend({ + page: async ({ page }, use) => { + await page.evaluate(() => window.localStorage.clear()); + await use(page); + }, +}); + +test('counter contract - increment function call works properly', async ({ page }) => { + await page.goto(WEB_SERVER_URL, { waitUntil: 'networkidle' }); + + await page.waitForTimeout(2000); + + const topUpWalletButton = page.getByText('Top-up Wallet'); + await topUpWalletButton.click(); + + await page.waitForTimeout(2000); + + const initialCounterValue = +page.getByTestId('counter').textContent; + + const incrementButton = page.getByText('Increment Counter'); + await incrementButton.click(); + + await page.waitForTimeout(2000); + + const counterValueAfterIncrement = +page.getByTestId('counter').textContent; + expect(counterValueAfterIncrement).toEqual(initialCounterValue + 1); +}); diff --git a/templates/vite/vitest.config.mts b/templates/vite/vitest.config.mts index a6ca0394a34..92dda13d6fc 100644 --- a/templates/vite/vitest.config.mts +++ b/templates/vite/vitest.config.mts @@ -2,4 +2,7 @@ import { defineConfig } from "vitest/config"; export default defineConfig({ esbuild: { target: "es2022" }, + test: { + exclude: ["**/test/ui/**"], + }, });