diff --git a/.github/workflows/playwright.yaml b/.github/workflows/playwright.yaml
index b0f110a6..cda3d60e 100644
--- a/.github/workflows/playwright.yaml
+++ b/.github/workflows/playwright.yaml
@@ -10,7 +10,8 @@ jobs:
strategy:
matrix:
tutorial:
- - "tests/how-to-test-contracts.spec.ts"
+ - "tests/erc20-paymaster.spec.ts"
+ - "tests/how-to-test-contracts.spec.ts"
steps:
- uses: actions/checkout@v4
@@ -21,4 +22,9 @@ jobs:
- name: Install Playwright Browsers
run: bun playwright install chromium --with-deps
- name: Run test for ${{ matrix.tutorial }}
- run: bun test:github ${{ matrix.tutorial }}
+ run: |
+ export TERM=xterm-256color
+ export COLUMNS=80
+ export LINES=24
+ script -q -c "bun test:github ${{ matrix.tutorial }}"
+
diff --git a/bun.lockb b/bun.lockb
index 192d1627..c16d1f9b 100755
Binary files a/bun.lockb and b/bun.lockb differ
diff --git a/components/TestAction.vue b/components/TestAction.vue
new file mode 100644
index 00000000..46e610aa
--- /dev/null
+++ b/components/TestAction.vue
@@ -0,0 +1,12 @@
+
+
+
+
+
diff --git a/content/tutorials/erc20-paymaster/10.index.md b/content/tutorials/erc20-paymaster/10.index.md
index 5b2aa52c..11ba1af4 100644
--- a/content/tutorials/erc20-paymaster/10.index.md
+++ b/content/tutorials/erc20-paymaster/10.index.md
@@ -40,19 +40,26 @@ Atlas is a smart contract IDE that lets you write, deploy, and interact with con
## Setup the Project
-1. Initiate a new project by running the command:
+First, initialize a new project by running the command:
- ```sh
- npx zksync-cli create custom-paymaster-tutorial --template hardhat_solidity
- ```
+:test-action{actionId="initialize-hardhat-project"}
- This creates a new ZKsync Era project called `custom-paymaster-tutorial` with a basic `Greeter` contract.
+```sh
+npx zksync-cli create custom-paymaster-tutorial --template hardhat_solidity
+```
-1. Navigate into the project directory:
+This creates a new ZKsync Era project called `custom-paymaster-tutorial` with a basic `Greeter` contract.
- ```sh
- cd custom-paymaster-tutorial
- ```
+Next, navigate into the project directory and install the dependencies:
+
+:test-action{actionId="wait-for-init"}
+:test-action{actionId="npm-install"}
+
+```sh
+cd custom-paymaster-tutorial && npm install --force
+```
+
+:test-action{actionId="wait-for-install"}
::callout{icon="i-heroicons-exclamation-circle"}
The template project includes multiple example contracts. Feel free to delete them.
@@ -213,7 +220,17 @@ _first_ check that the user provided enough allowance before calling `transferFr
## Paymaster Contract Full Code
-Create the `contracts/MyPaymaster.sol` file and copy/paste the following:
+Create the `contracts/MyPaymaster.sol` file with
+
+:test-action{actionId="create-contract-file"}
+
+```sh
+touch contracts/MyPaymaster.sol
+```
+
+and copy/paste the following:
+
+:test-action{actionId="add-paymaster-contract"}
```solidity
// SPDX-License-Identifier: MIT
@@ -338,10 +355,18 @@ contract MyPaymaster is IPaymaster {
## Create ERC20 Contract
-For the sake of simplicity we will use a modified OpenZeppelin ERC20 implementation:
+For the sake of simplicity we will use a modified OpenZeppelin ERC20 implementation.
+
+:test-action{actionId="create-erc20-contract-file"}
+
+```sh
+touch contracts/MyERC20.sol
+```
Create the `contracts/MyERC20.sol` file and copy/paste the following:
+:test-action{actionId="add-erc20-contract"}
+
```solidity [contracts/MyERC20.sol]
// SPDX-License-Identifier: UNLICENSED
@@ -378,7 +403,17 @@ It also mints some `MyERC20` tokens into the account we use to deploy the contra
In addition, the script sends `0.06ETH` to the paymaster contract so it can pay the transaction fees we send later on.
1. In the `deploy` folder, create the file `deploy-paymaster.ts` and copy/paste the following.
- Make sure the private key of the account used to deploy the contracts is configured in the `.env` file of the project.:
+
+ :test-action{actionId="create-deploy-file"}
+
+ ```sh
+ touch deploy/deploy-paymaster.ts
+ ```
+
+ Make sure the private key of the account used to deploy the contracts is configured in the `.env` file of the project.
+
+ :test-action{actionId="add-testing-private-key"}
+ :test-action{actionId="add-deploy-script"}
```ts [deploy-paymaster.ts]
import { deployContract, getWallet, getProvider } from "./utils";
@@ -416,16 +451,41 @@ In addition, the script sends `0.06ETH` to the paymaster contract so it can pay
1. Compile and the contracts from the project root:
- ```sh
+ :test-action{actionId="create-ts-config"}
+ :test-action{actionId="compile-contracts"}
+
+ ::code-group
+
+ ```bash [npx]
+ npx hardhat compile
+ ```
+
+ ```bash [yarn]
yarn hardhat compile
```
+ ::
+
1. Execute the deployment script:
- ```sh
+ :test-action{actionId="use-local-network"}
+ :test-action{actionId="start-local-network"}
+ :test-action{actionId="wait-for-hh-node"}
+ :test-action{actionId="temp-fix-import"}
+ :test-action{actionId="deploy-contracts"}
+
+ ::code-group
+
+ ```bash [npx]
+ npx hardhat deploy-zksync --script deploy-paymaster.ts
+ ```
+
+ ```bash [yarn]
yarn hardhat deploy-zksync --script deploy-paymaster.ts
```
+ ::
+
The output should be roughly the following:
```sh
@@ -461,10 +521,18 @@ Make sure you delete the `artifacts-zk` and `cache-zk` folders before recompilin
1. Create the `use-paymaster.ts` script in the `deploy` folder, replacing the parameter placeholders with the details from the previous deploy step.
+ :test-action{actionId="create-deploy-paymaster-file"}
+
+ ```sh
+ touch deploy/use-paymaster.ts
+ ```
+
::callout{icon="i-heroicons-exclamation-triangle"}
Make sure you use the private key of the wallet created by the previous script as that wallet contains the ERC20 tokens.
::
+ :test-action{actionId="add-use-paymaster"}
+
```ts [deploy/use-paymaster.ts]
import { utils, Wallet } from "zksync-ethers";
import { getWallet, getProvider } from "./utils";
@@ -534,12 +602,25 @@ Make sure you delete the `artifacts-zk` and `cache-zk` folders before recompilin
}
```
+ :test-action{actionId="paymaster-address"}
+ :test-action{actionId="token-address"}
+
1. Run the script:
- ```sh
+ :test-action{actionId="run-use-paymaster"}
+
+ ::code-group
+
+ ```bash [npx]
+ npx hardhat deploy-zksync --script use-paymaster.ts
+ ```
+
+ ```bash [yarn]
yarn hardhat deploy-zksync --script use-paymaster.ts
```
+ ::
+
The output should look something like this:
```txt
diff --git a/content/tutorials/how-to-test-contracts/10.index.md b/content/tutorials/how-to-test-contracts/10.index.md
index 85f8c8a0..b415a6db 100644
--- a/content/tutorials/how-to-test-contracts/10.index.md
+++ b/content/tutorials/how-to-test-contracts/10.index.md
@@ -26,7 +26,7 @@ During the alpha phase, ZKsync Era Test Nodes are currently undergoing developme
First, initialize a new Hardhat TypeScript project:
-
+:test-action{actionId="initialize-hardhat-project"}
```bash
npx hardhat init
@@ -36,7 +36,7 @@ Select the `Create a TypeScript project` option and install the sample project's
To install the `hardhat-zksync` plugin, execute the following command:
-
+:test-action{actionId="install-hh-zksync"}
::code-group
@@ -52,8 +52,7 @@ yarn add -D @matterlabs/hardhat-zksync
Once installed, add the plugin at the top of your `hardhat.config.ts` file.
-
+:test-action{actionId="import-zksync-config"}
```ts [hardhat.config.ts]
import "@matterlabs/hardhat-zksync";
@@ -63,7 +62,7 @@ import "@matterlabs/hardhat-zksync";
You can now safely run the **ZKsync Era Test Node** with the following command:
-
+:test-action{actionId="run-hh-node"}
::code-group
@@ -77,9 +76,8 @@ yarn hardhat node-zksync
::
-
-
-
+:test-action{actionId="wait-for-hh-node"}
+:test-action{actionId="test-hh-node"}
::callout{icon="i-heroicons-exclamation-circle"}
We'll want to verify the correctness of our installations and test if we can run a **ZKsync Era Test Node**,
@@ -107,8 +105,7 @@ To enable the usage of ZKsync Era Test Node in Hardhat,
add the `zksync:true` option to the hardhat network in the `hardhat.config.ts` file
and the `latest` version of `zksolc`:
-
+:test-action{actionId="zksync-hh-network"}
```ts
zksolc: {
@@ -128,7 +125,7 @@ it's necessary to use the `hardhat-chai-matchers` plugin.
In the root directory of your project, execute this command:
-
+:test-action{actionId="install-chai-ethers"}
::code-group
@@ -144,8 +141,7 @@ yarn add -D @nomicfoundation/hardhat-chai-matchers chai@4.3.6
After installing it, add the plugin at the top of your `hardhat.config.ts` file:
-
+:test-action{actionId="import-chai-matchers"}
```ts [hardhat.config.ts]
import "@nomicfoundation/hardhat-chai-matchers";
@@ -153,7 +149,7 @@ import "@nomicfoundation/hardhat-chai-matchers";
With the previous steps completed, your `hardhat.config.ts` file should now be properly configured to include settings for local testing.
-
+:test-action{actionId="compare-config"}
```ts [hardhat.config.ts]
import { HardhatUserConfig } from "hardhat/config";
@@ -182,7 +178,7 @@ To set up the environment for using chai matchers and writing tests, you'll need
Inside the **contracts** folder, rename the example contract file to **Greeter.sol**.
-
+:test-action{actionId="rename-greeter-file"}
```bash
mv contracts/Lock.sol contracts/Greeter.sol
@@ -190,7 +186,7 @@ mv contracts/Lock.sol contracts/Greeter.sol
Now replace the example contract in **Greeter.sol** with the new `Greeter` contract below:
-
+:test-action{actionId="create-greeter-contract"}
```solidity [Greeter.sol]
// SPDX-License-Identifier: MIT
@@ -223,7 +219,7 @@ Now you can create a test with the `hardhat-chai-matchers` plugin:
Inside the `/test` folder, rename the example test file to `test.ts`.
-
+:test-action{actionId="rename-test-file"}
```bash
mv test/Lock.ts test/test.ts
@@ -231,7 +227,7 @@ mv test/Lock.ts test/test.ts
Replace the old test with this example showcasing the functionalities of the contract:
-
+:test-action{actionId="create-test"}
```typescript
import * as hre from "hardhat";
@@ -284,7 +280,7 @@ describe("Greeter", function () {
Execute the following command in your terminal to run the tests:
-
+:test-action{actionId="run-test"}
::code-group
diff --git a/nuxt.config.ts b/nuxt.config.ts
index 90a91af5..61746ad1 100644
--- a/nuxt.config.ts
+++ b/nuxt.config.ts
@@ -11,6 +11,12 @@ export default defineNuxtConfig({
app: 'code',
},
},
+ components: [
+ {
+ path: '~/components',
+ global: true,
+ },
+ ],
content: {
navigation: {
fields: [
diff --git a/package.json b/package.json
index fe0d0fa2..0d1a40d7 100644
--- a/package.json
+++ b/package.json
@@ -50,6 +50,7 @@
"lint-staged": "^15.2.4",
"markdownlint": "^0.34.0",
"markdownlint-cli2": "^0.13.0",
+ "node-pty": "^1.0.0",
"pm2": "^5.4.2",
"prettier": "^3.2.5",
"prettier-eslint": "^16.3.0",
diff --git a/tests/configs/config.ts b/tests/configs/config.ts
new file mode 100644
index 00000000..420c6973
--- /dev/null
+++ b/tests/configs/config.ts
@@ -0,0 +1,18 @@
+import { steps as erc20PaymasterSteps } from './erc20-paymaster';
+import { steps as howToTestContractsSteps } from './how-to-test-contracts';
+
+export function getConfig(tutorialName: string) {
+ let steps;
+ switch (tutorialName) {
+ case 'erc20-paymaster':
+ steps = erc20PaymasterSteps;
+ break;
+ case 'how-to-test-contracts':
+ steps = howToTestContractsSteps;
+ break;
+ default:
+ break;
+ }
+
+ return steps;
+}
diff --git a/tests/configs/erc20-paymaster.ts b/tests/configs/erc20-paymaster.ts
new file mode 100644
index 00000000..f070a873
--- /dev/null
+++ b/tests/configs/erc20-paymaster.ts
@@ -0,0 +1,116 @@
+import type { IStepConfig } from '../utils/types';
+
+export const steps: IStepConfig = {
+ 'initialize-hardhat-project': {
+ action: 'runCommand',
+ prompts: 'Private key of the wallet:0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110|npm: ',
+ },
+ 'wait-for-init': {
+ action: 'wait',
+ timeout: 5000,
+ },
+ 'npm-install': {
+ action: 'runCommand',
+ },
+ 'wait-for-install': {
+ action: 'wait',
+ timeout: 5000,
+ },
+ 'create-contract-file': {
+ action: 'runCommand',
+ commandFolder: 'tests-output/custom-paymaster-tutorial',
+ },
+ 'add-paymaster-contract': {
+ action: 'writeToFile',
+ filepath: 'tests-output/custom-paymaster-tutorial/contracts/MyPaymaster.sol',
+ },
+ 'create-erc20-contract-file': {
+ action: 'runCommand',
+ commandFolder: 'tests-output/custom-paymaster-tutorial',
+ },
+ 'add-erc20-contract': {
+ action: 'writeToFile',
+ filepath: 'tests-output/custom-paymaster-tutorial/contracts/MyERC20.sol',
+ },
+ 'create-deploy-file': {
+ action: 'runCommand',
+ commandFolder: 'tests-output/custom-paymaster-tutorial',
+ },
+ 'add-testing-private-key': {
+ action: 'modifyFile',
+ filepath: 'tests-output/custom-paymaster-tutorial/.env',
+ atLine: 1,
+ removeLines: [1],
+ useSetData: 'WALLET_PRIVATE_KEY=0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110',
+ },
+ 'add-deploy-script': {
+ action: 'writeToFile',
+ filepath: 'tests-output/custom-paymaster-tutorial/deploy/deploy-paymaster.ts',
+ },
+ 'create-ts-config': {
+ action: 'runCommand',
+ commandFolder: 'tests-output/custom-paymaster-tutorial',
+ useSetCommand: 'touch tsconfig.json',
+ },
+ 'compile-contracts': {
+ action: 'runCommand',
+ commandFolder: 'tests-output/custom-paymaster-tutorial',
+ },
+ 'use-local-network': {
+ action: 'modifyFile',
+ filepath: 'tests-output/custom-paymaster-tutorial/hardhat.config.ts',
+ atLine: 6,
+ removeLines: [6],
+ useSetData: ' defaultNetwork: "inMemoryNode",',
+ },
+ 'start-local-network': {
+ action: 'runCommand',
+ commandFolder: 'tests-output/custom-paymaster-tutorial',
+ useSetCommand: "bun pm2 start 'npx hardhat node-zksync' --name hh-zknode",
+ },
+ 'wait-for-hh-node': {
+ action: 'wait',
+ timeout: 7000,
+ },
+ 'temp-fix-import': {
+ action: 'modifyFile',
+ filepath: 'tests-output/custom-paymaster-tutorial/deploy/utils.ts',
+ atLine: 4,
+ removeLines: [4],
+ useSetData: "import * as dotenv from 'dotenv';",
+ },
+ 'deploy-contracts': {
+ action: 'runCommand',
+ commandFolder: 'tests-output/custom-paymaster-tutorial',
+ },
+ 'create-deploy-paymaster-file': {
+ action: 'runCommand',
+ commandFolder: 'tests-output/custom-paymaster-tutorial',
+ },
+ 'add-use-paymaster': {
+ action: 'writeToFile',
+ filepath: 'tests-output/custom-paymaster-tutorial/deploy/use-paymaster.ts',
+ },
+ 'paymaster-address': {
+ action: 'modifyFile',
+ filepath: 'tests-output/custom-paymaster-tutorial/deploy/use-paymaster.ts',
+ atLine: 7,
+ removeLines: [7],
+ getContractId:
+ 'tests-output/custom-paymaster-tutorial/deployments-zk/inMemoryNode/contracts/MyPaymaster.sol/MyPaymaster.json',
+ useSetData: "const PAYMASTER_ADDRESS = '<*GET_CONTRACT_ID*>';",
+ },
+ 'token-address': {
+ action: 'modifyFile',
+ filepath: 'tests-output/custom-paymaster-tutorial/deploy/use-paymaster.ts',
+ atLine: 10,
+ removeLines: [10],
+ getContractId:
+ 'tests-output/custom-paymaster-tutorial/deployments-zk/inMemoryNode/contracts/MyERC20.sol/MyERC20.json',
+ useSetData: "const TOKEN_ADDRESS = '<*GET_CONTRACT_ID*>';",
+ },
+ 'run-use-paymaster': {
+ action: 'runCommand',
+ commandFolder: 'tests-output/custom-paymaster-tutorial',
+ },
+};
diff --git a/tests/configs/how-to-test-contracts.ts b/tests/configs/how-to-test-contracts.ts
new file mode 100644
index 00000000..d36c1c28
--- /dev/null
+++ b/tests/configs/how-to-test-contracts.ts
@@ -0,0 +1,68 @@
+import type { IStepConfig } from '../utils/types';
+
+export const steps: IStepConfig = {
+ 'initialize-hardhat-project': {
+ action: 'runCommand',
+ },
+ 'install-hh-zksync': {
+ action: 'runCommand',
+ commandFolder: 'tests-output/hardhat-project',
+ },
+ 'import-zksync-config': {
+ action: 'modifyFile',
+ filepath: 'tests-output/hardhat-project/hardhat.config.ts',
+ atLine: 3,
+ },
+ 'run-hh-node': {
+ action: 'runCommand',
+ commandFolder: 'tests-output/hardhat-project',
+ preCommand: "bun pm2 start '' --name hh-zknode",
+ },
+ 'wait-for-hh-node': {
+ action: 'wait',
+ timeout: 7000,
+ },
+ 'test-hh-node': {
+ action: 'checkIfBalanceIsZero',
+ networkUrl: 'http://127.0.0.1:8011',
+ address: '0xe2b8Cb53a43a56d4d2AB6131C81Bd76B86D3AFe5',
+ },
+ 'zksync-hh-network': {
+ action: 'modifyFile',
+ filepath: 'tests-output/hardhat-project/hardhat.config.ts',
+ atLine: 7,
+ },
+ 'install-chai-ethers': {
+ action: 'runCommand',
+ commandFolder: 'tests-output/hardhat-project',
+ },
+ 'import-chai-matchers': {
+ action: 'modifyFile',
+ filepath: 'tests-output/hardhat-project/hardhat.config.ts',
+ atLine: 4,
+ },
+ 'compare-config': {
+ action: 'compareToFile',
+ filepath: 'tests-output/hardhat-project/hardhat.config.ts',
+ },
+ 'rename-greeter-file': {
+ action: 'runCommand',
+ commandFolder: 'tests-output/hardhat-project',
+ },
+ 'create-greeter-contract': {
+ action: 'writeToFile',
+ filepath: 'tests-output/hardhat-project/contracts/Greeter.sol',
+ },
+ 'rename-test-file': {
+ action: 'runCommand',
+ commandFolder: 'tests-output/hardhat-project',
+ },
+ 'create-test': {
+ action: 'writeToFile',
+ filepath: 'tests-output/hardhat-project/test/test.ts',
+ },
+ 'run-test': {
+ action: 'runCommand',
+ commandFolder: 'tests-output/hardhat-project',
+ },
+};
diff --git a/tests/erc20-paymaster.spec.ts b/tests/erc20-paymaster.spec.ts
new file mode 100644
index 00000000..358c2d79
--- /dev/null
+++ b/tests/erc20-paymaster.spec.ts
@@ -0,0 +1,12 @@
+import { test } from '@playwright/test';
+import { setupAndRunTest } from './utils/runTest';
+
+test('Build an ERC20 custom paymaster', async ({ page, context }) => {
+ await setupAndRunTest(
+ page,
+ context,
+ 'custom-paymaster-tutorial',
+ ['http://localhost:3000/tutorials/erc20-paymaster'],
+ 'erc20-paymaster'
+ );
+});
diff --git a/tests/how-to-test-contracts.spec.ts b/tests/how-to-test-contracts.spec.ts
index 94e29447..262b0086 100644
--- a/tests/how-to-test-contracts.spec.ts
+++ b/tests/how-to-test-contracts.spec.ts
@@ -1,16 +1,12 @@
import { test } from '@playwright/test';
-import { setupFolders, stopServers, startLocalServer } from './utils/setup';
-import { runTest } from './utils/runTest';
+import { setupAndRunTest } from './utils/runTest';
-test('how-to-test-contracts-with-hardhat', async ({ page, context }) => {
- // SETUP
- await startLocalServer(page);
- await context.grantPermissions(['clipboard-read', 'clipboard-write']);
- await setupFolders('hardhat-test-example');
-
- // TEST
- await runTest(page, 'http://localhost:3000/tutorials/how-to-test-contracts');
-
- // SHUT DOWN ANY RUNNING PROJECTS
- stopServers();
+test('How to test smart contracts with Hardhat', async ({ page, context }) => {
+ await setupAndRunTest(
+ page,
+ context,
+ 'hardhat-project',
+ ['http://localhost:3000/tutorials/how-to-test-contracts'],
+ 'how-to-test-contracts'
+ );
});
diff --git a/tests/utils/files.ts b/tests/utils/files.ts
index c4be9197..c22b96aa 100644
--- a/tests/utils/files.ts
+++ b/tests/utils/files.ts
@@ -15,10 +15,21 @@ export async function modifyFile(
addSpacesBefore?: number,
addSpacesAfter?: number,
atLine?: number,
- removeLines?: string,
- useSetData?: string
+ removeLines?: number[],
+ useSetData?: string,
+ deploymentFilePath?: string
) {
let contentText = useSetData;
+
+ if (deploymentFilePath) {
+ const contractId = getContractId(deploymentFilePath);
+ if (contentText?.includes('<*GET_CONTRACT_ID*>')) {
+ contentText = contentText.replace('<*GET_CONTRACT_ID*>', contractId);
+ } else {
+ contentText = contractId;
+ }
+ }
+
if (!contentText) {
contentText = await clickCopyButton(page, buttonName);
}
@@ -31,9 +42,8 @@ export async function modifyFile(
} else {
const lines = readFileSync(filePath, 'utf8').split('\n');
if (removeLines) {
- const removeLinesArray = JSON.parse(removeLines);
- removeLinesArray.forEach((lineNumber: string) => {
- lines[Number.parseInt(lineNumber) - 1] = '~~~REMOVE~~~';
+ removeLines.forEach((lineNumber: number) => {
+ lines[lineNumber - 1] = '~~~REMOVE~~~';
});
}
if (atLine) {
@@ -66,3 +76,9 @@ export function compareOutputs(expected: string, actual: string) {
expect(trimmedLineA).toEqual(trimmedLineB);
});
}
+
+function getContractId(deploymentFilePath: string) {
+ const deploymentFile = readFileSync(deploymentFilePath, { encoding: 'utf8' });
+ const json = JSON.parse(deploymentFile);
+ return json.entries[0].address;
+}
diff --git a/tests/utils/getTestActions.ts b/tests/utils/getTestActions.ts
index aba344ba..1d303e44 100644
--- a/tests/utils/getTestActions.ts
+++ b/tests/utils/getTestActions.ts
@@ -1,7 +1,7 @@
import { type Page, expect } from '@playwright/test';
export async function getTestActions(page: Page) {
- const testActions = await page.$$eval('span[data-name]', (elements: Element[]) => {
+ const testActions = await page.$$eval('span[data-test-action]', (elements: Element[]) => {
return elements.map((el) => {
const dataAttributes: {
[key: string]: string;
diff --git a/tests/utils/runCommand.ts b/tests/utils/runCommand.ts
index 0f70ea57..32e342b2 100644
--- a/tests/utils/runCommand.ts
+++ b/tests/utils/runCommand.ts
@@ -3,17 +3,21 @@ import { execSync } from 'node:child_process';
import { clickCopyButton } from './button';
import fs from 'fs';
import { join } from 'path';
+import os from 'os';
+import pty from 'node-pty';
export async function runCommand(
page: Page,
buttonName: string,
goToFolder: string = 'tests-output',
projectFolder: string = 'hardhat-project',
- preCommand?: string
+ preCommand?: string,
+ useSetCommand?: string,
+ prompts?: string
) {
const copied = await clickCopyButton(page, buttonName);
console.log('COPIED', copied);
- let command = copied;
+ let command = useSetCommand ?? copied;
const newHardhatProject = command.includes('npx hardhat init');
if (newHardhatProject) {
@@ -31,7 +35,11 @@ export async function runCommand(
command = `cd ${goToFolder} && ${command}`;
}
- run(command);
+ if (prompts) {
+ await runWithPrompts(page, command, prompts);
+ } else {
+ run(command);
+ }
}
}
@@ -77,3 +85,37 @@ function copyFolder(source: string, destination: string) {
copyRecursive(source, destination);
}
+
+export async function runWithPrompts(page: Page, command: string, prompts: string) {
+ const shell = os.platform() === 'win32' ? 'powershell.exe' : 'bash';
+
+ const ptyProcess = pty.spawn(shell, [], {
+ name: 'xterm-color',
+ cols: 80,
+ rows: 30,
+ cwd: process.cwd(),
+ env: process.env,
+ });
+
+ const promptsArray = prompts.split('|').map((pair) => {
+ const [prompt, answer] = pair.split(':');
+ return { prompt, answer };
+ });
+
+ ptyProcess.onData((data) => {
+ console.log('DATA:', data);
+
+ for (let index = 0; index < promptsArray.length; index++) {
+ const promptObject = promptsArray[index];
+ if (data.includes(promptObject.prompt)) {
+ console.log('FOUND PROMPT:', promptObject.prompt);
+ ptyProcess.write(promptObject.answer + '\r');
+ }
+ }
+ });
+
+ ptyProcess.write(command + '\r');
+
+ await page.waitForTimeout(35000);
+ console.log('waited 35 seconds');
+}
diff --git a/tests/utils/runTest.ts b/tests/utils/runTest.ts
index 90ef725e..ba3c81c5 100644
--- a/tests/utils/runTest.ts
+++ b/tests/utils/runTest.ts
@@ -1,12 +1,38 @@
-import type { Page } from '@playwright/test';
+import type { BrowserContext, Page } from '@playwright/test';
import { runCommand } from './runCommand';
import { getTestActions } from './getTestActions';
import { visit } from './visit';
import { compareToFile, modifyFile, writeToFile } from './files';
import { checkIfBalanceIsZero } from './queries';
+import { setupFolders, startLocalServer, stopServers } from './setup';
+import { getConfig } from '../configs/config';
+import type { IStepConfig } from './types';
-export async function runTest(page: Page, url: string) {
+export async function setupAndRunTest(
+ page: Page,
+ context: BrowserContext,
+ folderName: string,
+ pageUrls: string[],
+ tutorialName: string
+) {
+ // SETUP
+ await startLocalServer(page);
+ await context.grantPermissions(['clipboard-read', 'clipboard-write']);
+ await setupFolders(folderName);
+
+ const config = getConfig(tutorialName);
+
+ // TEST
+ for (const pageUrl of pageUrls) {
+ await runTest(page, pageUrl, config!);
+ }
+
+ // SHUT DOWN ANY RUNNING PROJECTS
+ stopServers();
+}
+
+export async function runTest(page: Page, url: string, config: IStepConfig) {
await visit(page, url);
console.log('GETTING TEST ACTIONS');
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -16,42 +42,47 @@ export async function runTest(page: Page, url: string) {
for (const step of steps) {
console.log('STEP:', step);
await page.waitForTimeout(1000);
- switch (step['data-name']) {
+ const stepID = step['id'];
+ const stepData = config[stepID];
+ switch (stepData.action) {
case 'runCommand':
await runCommand(
page,
- step.id,
- step['data-command-folder'],
- step['data-project-folder'],
- step['data-pre-command']
+ stepID,
+ stepData.commandFolder,
+ stepData.projectFolder,
+ stepData.preCommand,
+ stepData.useSetCommand,
+ stepData.prompts
);
break;
case 'wait':
- await page.waitForTimeout(Number.parseInt(step['data-timeout']));
+ await page.waitForTimeout(stepData.timeout);
break;
case 'writeToFile':
- await writeToFile(page, step.id, step['data-filepath']);
+ await writeToFile(page, stepID, stepData.filepath);
break;
case 'modifyFile':
await modifyFile(
page,
- step.id,
- step['data-filepath'],
- Number.parseInt(step['data-add-spaces-before']),
- step['data-add-spaces-after'],
- Number.parseInt(step['data-at-line']),
- step['data-remove-lines'],
- step['data-use-set-data']
+ stepID,
+ stepData.filepath,
+ stepData.addSpacesBefore,
+ stepData.addSpacesAfter,
+ stepData.atLine,
+ stepData.removeLines,
+ stepData.useSetData,
+ stepData.getContractId
);
break;
case 'compareToFile':
- await compareToFile(page, step.id, step['data-filepath']);
+ await compareToFile(page, stepID, stepData.filepath);
break;
case 'checkIfBalanceIsZero':
- await checkIfBalanceIsZero(step['data-network-url'], step['data-address']);
+ await checkIfBalanceIsZero(stepData.networkUrl, stepData.address);
break;
default:
- console.log('STEP NOT FOUND:', step);
+ console.log('STEP NOT FOUND:', stepData);
}
}
}
diff --git a/tests/utils/types.ts b/tests/utils/types.ts
new file mode 100644
index 00000000..3fa7f347
--- /dev/null
+++ b/tests/utils/types.ts
@@ -0,0 +1,46 @@
+export interface IStepConfig {
+ [key: string]: IStep;
+}
+
+export type IStep = IRunCommand | IWait | IWriteToFile | IModifyFile | ICompareToFile | ICheckIfBalanceIsZero;
+
+export interface IRunCommand {
+ action: 'runCommand';
+ commandFolder?: string;
+ projectFolder?: string;
+ preCommand?: string;
+ useSetCommand?: string;
+ prompts?: string;
+}
+
+export interface IWait {
+ action: 'wait';
+ timeout: number;
+}
+
+export interface IWriteToFile {
+ action: 'writeToFile';
+ filepath: string;
+}
+
+export interface IModifyFile {
+ action: 'modifyFile';
+ filepath: string;
+ addSpacesBefore?: number;
+ addSpacesAfter?: number;
+ atLine?: number;
+ removeLines?: number[];
+ useSetData?: string;
+ getContractId?: string;
+}
+
+export interface ICompareToFile {
+ action: 'compareToFile';
+ filepath: string;
+}
+
+export interface ICheckIfBalanceIsZero {
+ action: 'checkIfBalanceIsZero';
+ networkUrl: string;
+ address: string;
+}