diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 67eb42ff7..f6520af5e 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -38,7 +38,7 @@ jobs: - name: Start TON Connect Bridge run: | - echo "Starting TON Connect Bridge service with docker-compose.memory.yml..." + echo "Starting TON Connect Bridge service with docker-compose.bridge.yml..." docker compose -f docker-compose.bridge.yml up -d echo "Waiting for services to be ready..." sleep 2 @@ -69,7 +69,6 @@ jobs: fi echo "πŸš€ TON Connect Bridge service is now running and accessible at http://localhost:8081/bridge" - # uses: ton-connect/bridge/actions/local@master - name: Run e2e specs run: xvfb-run pnpm --filter demo-wallet e2e @@ -89,4 +88,4 @@ jobs: env: ALLURE_BASE_URL: ${{ secrets.ALLURE_BASE_URL }} ALLURE_API_TOKEN: ${{ secrets.ALLURE_API_TOKEN }} - ALLURE_PROJECT_ID: ${{ secrets.ALLURE_PROJECT_ID }} \ No newline at end of file + ALLURE_PROJECT_ID: ${{ secrets.ALLURE_PROJECT_ID }} diff --git a/apps/demo-wallet/e2e/connect.spec.ts b/apps/demo-wallet/e2e/connect.spec.ts new file mode 100644 index 000000000..614794fa9 --- /dev/null +++ b/apps/demo-wallet/e2e/connect.spec.ts @@ -0,0 +1,91 @@ +import { expect } from '@playwright/test'; +import { allure } from 'allure-playwright'; + +import { testWith } from './qa'; +import { demoWalletFixture } from './demo-wallet'; +import { AllureApiClient, createAllureConfig, getTestCaseData, extractAllureId } from './utils'; + +const test = testWith( + demoWalletFixture({ + extensionPath: 'dist-extension', + mnemonic: + process.env.WALLET_MNEMONIC || + 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about', + appUrl: process.env.DAPP_URL || 'https://allure-test-runner.vercel.app/e2e ' + }, 0), +); + +let allureClient: AllureApiClient; + +test.beforeAll(async () => { + try { + // Π‘ΠΎΠ·Π΄Π°Π΅ΠΌ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ Allure + const config = createAllureConfig(); + + // Π‘ΠΎΠ·Π΄Π°Π΅ΠΌ ΠΊΠ»ΠΈΠ΅Π½Ρ‚ Allure + allureClient = new AllureApiClient(config); + } catch (error) { + console.error('Error creating allure client:', error); + throw error; + } +}); + +async function runConnectTest({ wallet, app, widget }: { wallet: any; app: any; widget: any }, testInfo: any) { + // ИзвлСкаСм allureId ΠΈΠ· названия тСста + const allureId = extractAllureId(testInfo.title); + + // УстанавливаСм Allure Π°Π½Π½ΠΎΡ‚Π°Ρ†ΠΈΠΈ + if (allureId) { + await allure.allureId(allureId); + await allure.owner('e.kurilenko'); + } + + // Π˜Π½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·ΠΈΡ€ΡƒΠ΅ΠΌ ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Π΅ для Π΄Π°Π½Π½Ρ‹Ρ… тСст-кСйса + let precondition: string = ''; + let expectedResult: string = ''; + let isPositiveCase: boolean = true; + + if (allureId && allureClient) { + try { + const testCaseData = await getTestCaseData(allureClient, allureId); + precondition = testCaseData.precondition; + expectedResult = testCaseData.expectedResult; + isPositiveCase = testCaseData.isPositiveCase; + } catch (error) { + console.error('Error getting test case data:', error); + } + } else { + console.warn('AllureId not found in test title or client not available'); + } + + await app.getByTestId('connectPrecondition').fill(precondition||''); + await app.getByTestId('connectExpectedResult').fill(expectedResult); + + await expect(app.getByTestId('connect-button')).toHaveText('Connect Wallet'); + //await app.locator('#connect-button').click(); + await app.getByTestId('connect-button').click(); + + await widget.connectUrlButton.waitFor({ state: 'visible' }); + await widget.connectUrlButton.click(); + const handle = await widget.page.evaluateHandle(() => navigator.clipboard.readText()); + console.log(handle); + //return await handle.jsonValue(); + + await wallet.connect(true, await handle.jsonValue()); + + //await wallet.connect(); + + await expect(app.getByTestId('connectValidation')).toHaveText('Validation Passed'); +} + +test('Connect @allureId(1933)', async ({ wallet, app, widget }) => { + await runConnectTest({ wallet, app, widget }, test.info()); +}); + +test('Connect @allureId(1900)', async ({ wallet, app, widget }) => { + await runConnectTest({ wallet, app, widget }, test.info()); +}); + +test('Connect @allureId(1902)', async ({ wallet, app, widget }) => { + await runConnectTest({ wallet, app, widget }, test.info()); +}); \ No newline at end of file diff --git a/apps/demo-wallet/e2e/demo-wallet/DemoWallet.ts b/apps/demo-wallet/e2e/demo-wallet/DemoWallet.ts index ba4c66675..b914a2bde 100644 --- a/apps/demo-wallet/e2e/demo-wallet/DemoWallet.ts +++ b/apps/demo-wallet/e2e/demo-wallet/DemoWallet.ts @@ -41,8 +41,19 @@ export class DemoWallet extends WalletExtension { await app.locator(testSelector('tonconnect-url')).fill(url); await app.locator(testSelector('tonconnect-process')).click(); await app.locator(testSelector('request'), { hasText: 'Connect Request' }).waitFor({ state: 'visible' }); - await app.locator(testSelector('connect')).waitFor({ state: 'attached', timeout: 10_000 }); - await app.locator(testSelector('connect')).click(); + await app.locator(testSelector('connect-approve')).waitFor({ state: 'attached', timeout: 10_000 }); + await app.locator(testSelector('connect-approve')).click(); + await app.locator(testSelector('request')).waitFor({ state: 'detached', timeout: 10_000 }); + await app.close(); + } + + async connect(confirm: boolean = true, url: string = ''): Promise { + const app = await this.open(); + await app.locator(testSelector('tonconnect-url')).fill(url); + await app.locator(testSelector('tonconnect-process')).click(); + await app.locator(testSelector('request'), { hasText: 'Connect Request' }).waitFor({ state: 'visible' }); + await app.locator(testSelector('connect-approve')).waitFor({ state: 'attached', timeout: 10_000 }); + await app.locator(testSelector(confirm? 'connect-approve':'connect-reject')).click(); await app.locator(testSelector('request')).waitFor({ state: 'detached', timeout: 10_000 }); await app.close(); } @@ -62,11 +73,6 @@ export class DemoWallet extends WalletExtension { await app.close(); } - async connect(_confirm?: boolean): Promise { - // TODO implement DemoWallet connect - throw new Error('DemoWallet connect not implemented'); - } - async accept(_confirm: boolean = true): Promise { // TODO implement DemoWallet accept throw new Error('DemoWallet accept not implemented'); diff --git a/apps/demo-wallet/e2e/sendTransaction.spec.ts b/apps/demo-wallet/e2e/sendTransaction.spec.ts new file mode 100644 index 000000000..ba0b49099 --- /dev/null +++ b/apps/demo-wallet/e2e/sendTransaction.spec.ts @@ -0,0 +1,234 @@ +import { expect } from '@playwright/test'; +import { AllureApiClient, createAllureConfig, getTestCaseData, extractAllureId } from './utils'; +import { config } from 'dotenv'; +import { resolve, dirname } from 'path'; +import { fileURLToPath } from 'url'; +import { allure } from 'allure-playwright'; +import { testWith } from './qa'; +import { demoWalletFixture } from './demo-wallet'; + +// Π‘ΠΎΠ·Π΄Π°Π΅ΠΌ тСст с использованиСм demoWalletFixture +const test = testWith( + demoWalletFixture({ + extensionPath: 'dist-extension', + mnemonic: process.env.WALLET_MNEMONIC || 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about', + appUrl: process.env.DAPP_URL || 'https://allure-test-runner.vercel.app/e2e' + }), +); +// Global variable for storing the Allure client +let allureClient: AllureApiClient; + +// Function for extracting allureId from the test title + +// universal function for executing SendTransaction test +async function runSendTransactionTest( + { wallet, app, widget }: { wallet: any; app: any; widget: any }, + testInfo: any +) { + const allureId = extractAllureId(testInfo.title); + + if (allureId) { + await allure.allureId(allureId); + await allure.owner('e.kurilenko'); + } + + // Π˜Π½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·ΠΈΡ€ΡƒΠ΅ΠΌ ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Π΅ для Π΄Π°Π½Π½Ρ‹Ρ… тСст-кСйса + let precondition: string = ""; + let expectedResult: string = ""; + let isPositiveCase: boolean = true; + + if (allureId && allureClient) { + try { + const testCaseData = await getTestCaseData(allureClient, allureId); + precondition = testCaseData.precondition; + expectedResult = testCaseData.expectedResult; + isPositiveCase = testCaseData.isPositiveCase; + } catch (error) { + console.error('Error getting test case data:', error); + } + } else { + console.warn('AllureId not found in test title or client not available'); + } + + await expect(widget.connectButtonText).toHaveText('Connect Wallet'); + await wallet.connectBy(await widget.connectUrl()); + await expect(widget.connectButtonText).toHaveText('UQC8…t2Iv'); + + await app.locator('#sendTxPrecondition').fill(precondition); + await app.locator('#sendTxExpectedResult').fill(expectedResult); + await app.locator('#send-transaction-button').click(); + //await app.getByRole('button', {name: 'Send Transaction'}).click(); + + await wallet.sendTransaction(true, isPositiveCase); + + await expect(app.getByTestId('sendTransactionValidation')).toHaveText('Validation Passed'); +} + +test.beforeAll(async () => { + try { + // Π‘ΠΎΠ·Π΄Π°Π΅ΠΌ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ Allure + const config = createAllureConfig(); + + // Π‘ΠΎΠ·Π΄Π°Π΅ΠΌ ΠΊΠ»ΠΈΠ΅Π½Ρ‚ Allure + allureClient = new AllureApiClient(config); + + } catch (error) { + console.error('Error creating allure client:', error); + throw error; + } +}); + +// SendTransaction validation tests +test('[address] Error if absent @allureId(1847)', async ({ wallet, app, widget }) => { + await runSendTransactionTest({ wallet, app, widget }, test.info()); +}); + +test('[address] Error if in HEX format @allureId(1870)', async ({ wallet, app, widget }) => { + await runSendTransactionTest({ wallet, app, widget }, test.info()); +}); + +test('[address] Error if invalid value @allureId(1856)', async ({ wallet, app, widget }) => { + await runSendTransactionTest({ wallet, app, widget }, test.info()); +}); + +test('[address] Success if in bounceable format @allureId(1852)', async ({ wallet, app, widget }) => { + await runSendTransactionTest({ wallet, app, widget }, test.info()); +}); + +test('[address] Success if in non-bounceable format @allureId(1853)', async ({ wallet, app, widget }) => { + await runSendTransactionTest({ wallet, app, widget }, test.info()); +}); + +test('[amount] Error if absent @allureId(1873)', async ({ wallet, app, widget }) => { + await runSendTransactionTest({ wallet, app, widget }, test.info()); +}); + +test('[amount] Error if as a number @allureId(1857)', async ({ wallet, app, widget }) => { + await runSendTransactionTest({ wallet, app, widget }, test.info()); +}); + +test('[amount] Error if insufficient balance @allureId(1871)', async ({ wallet, app, widget }) => { + await runSendTransactionTest({ wallet, app, widget }, test.info()); +}); + +test('[amount] Success if \'0\' @allureId(1980)', async ({ wallet, app, widget }) => { + await runSendTransactionTest({ wallet, app, widget }, test.info()); +}); + +test('[amount] Success if as a string @allureId(1849)', async ({ wallet, app, widget }) => { + await runSendTransactionTest({ wallet, app, widget }, test.info()); +}); + +test('[from] Error if address doesn\'t match the user\'s wallet address @allureId(1877)', async ({ wallet, app, widget }) => { + await runSendTransactionTest({ wallet, app, widget }, test.info()); +}); + +test('[from] Error if invalid value @allureId(1848)', async ({ wallet, app, widget }) => { + await runSendTransactionTest({ wallet, app, widget }, test.info()); +}); + +test('[from] Success if in bounceable format @allureId(1878)', async ({ wallet, app, widget }) => { + await runSendTransactionTest({ wallet, app, widget }, test.info()); +}); + +test('[from] Success if in HEX format @allureId(1855)', async ({ wallet, app, widget }) => { + await runSendTransactionTest({ wallet, app, widget }, test.info()); +}); + +test('[from] Success if in non-bounceable format @allureId(1862)', async ({ wallet, app, widget }) => { + await runSendTransactionTest({ wallet, app, widget }, test.info()); +}); + +test('[messages] Error if array is empty @allureId(1864)', async ({ wallet, app, widget }) => { + await runSendTransactionTest({ wallet, app, widget }, test.info()); +}); + +test('[messages] Error if contains invalid message @allureId(1869)', async ({ wallet, app, widget }) => { + await runSendTransactionTest({ wallet, app, widget }, test.info()); +}); + +test('[messages] Success if contains maximum messages @allureId(1959)', async ({ wallet, app, widget }) => { + await runSendTransactionTest({ wallet, app, widget }, test.info()); +}); + +test('[network] Error if \'-3\' (testnet) @allureId(1876)', async ({ wallet, app, widget }) => { + await runSendTransactionTest({ wallet, app, widget }, test.info()); +}); + +test('[network] Error if as a number @allureId(1860)', async ({ wallet, app, widget }) => { + await runSendTransactionTest({ wallet, app, widget }, test.info()); +}); + +test('[network] Success if \'-239\' (mainnet) @allureId(1875)', async ({ wallet, app, widget }) => { + await runSendTransactionTest({ wallet, app, widget }, test.info()); +}); + +test('[payload] Error if invalid value @allureId(1872)', async ({ wallet, app, widget }) => { + await runSendTransactionTest({ wallet, app, widget }, test.info()); +}); + +test('[payload] Success if absent @allureId(1854)', async ({ wallet, app, widget }) => { + await runSendTransactionTest({ wallet, app, widget }, test.info()); +}); + +test('[payload] Success if valid value @allureId(1879)', async ({ wallet, app, widget }) => { + await runSendTransactionTest({ wallet, app, widget }, test.info()); +}); + +test('[stateInit] Error if invalid value @allureId(1874)', async ({ wallet, app, widget }) => { + await runSendTransactionTest({ wallet, app, widget }, test.info()); +}); + +test('[stateInit] Success if absent @allureId(1859)', async ({ wallet, app, widget }) => { + await runSendTransactionTest({ wallet, app, widget }, test.info()); +}); + +test('[stateInit] Success if valid value @allureId(1850)', async ({ wallet, app, widget }) => { + await runSendTransactionTest({ wallet, app, widget }, test.info()); +}); + +test('[validUntil] Success if absent @allureId(1866)', async ({ wallet, app, widget }) => { + await runSendTransactionTest({ wallet, app, widget }, test.info()); +}); + +test('[validUntil] Error if as a string @allureId(1865)', async ({ wallet, app, widget }) => { + await runSendTransactionTest({ wallet, app, widget }, test.info()); +}); + +test('[validUntil] Error if expired @allureId(1861)', async ({ wallet, app, widget }) => { + await runSendTransactionTest({ wallet, app, widget }, test.info()); +}); + +test('[validUntil] Error if has expired during confirmation @allureId(1863)', async ({ wallet, app, widget }) => { + await runSendTransactionTest({ wallet, app, widget }, test.info()); +}); + +test('[validUntil] Error if NaN @allureId(1867)', async ({ wallet, app, widget }) => { + await runSendTransactionTest({ wallet, app, widget }, test.info()); +}); + +test('[validUntil] Error if NULL @allureId(1868)', async ({ wallet, app, widget }) => { + await runSendTransactionTest({ wallet, app, widget }, test.info()); +}); + +test('[validUntil] Success if less then in 5 minutes @allureId(1851)', async ({ wallet, app, widget }) => { + await runSendTransactionTest({ wallet, app, widget }, test.info()); +}); + +test('[validUntil] Success if more then in 5 minutes @allureId(1858)', async ({ wallet, app, widget }) => { + await runSendTransactionTest({ wallet, app, widget }, test.info()); +}); + +// Merkle proof/update tests +test('Send merkle proof @allureId(1916)', async ({ wallet, app, widget }) => { + await runSendTransactionTest({ wallet, app, widget }, test.info()); +}); + +test('Send merkle update @allureId(1917)', async ({ wallet, app, widget }) => { + await runSendTransactionTest({ wallet, app, widget }, test.info()); +}); + +// Jetton minting tests +test('Minting Jetton with Deployed Contract @allureId(1899)', async ({ wallet, app, widget }) => { + await runSendTransactionTest({ wallet, app, widget }, test.info()); +}); \ No newline at end of file diff --git a/apps/demo-wallet/e2e/signData.spec.ts b/apps/demo-wallet/e2e/signData.spec.ts index 0e68b43bb..0c4b8b538 100644 --- a/apps/demo-wallet/e2e/signData.spec.ts +++ b/apps/demo-wallet/e2e/signData.spec.ts @@ -1,37 +1,83 @@ -import path from 'path'; -import { fileURLToPath } from 'url'; -import { dirname } from 'path'; - -import * as allure from 'allure-js-commons'; - import { testWith } from './qa'; import { demoWalletFixture } from './demo-wallet'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -const extensionPath = path.join(__dirname, '../dist-extension'); +import { AllureApiClient, createAllureConfig, getTestCaseData, extractAllureId } from './utils'; +import { allure, expect } from 'allure-playwright'; const test = testWith( demoWalletFixture({ - extensionPath: extensionPath, - mnemonic: process.env.WALLET_MNEMONIC!, - appUrl: 'https://tonconnect-demo-dapp-with-react-ui.vercel.app/', + extensionPath: 'dist-extension', + mnemonic: process.env.WALLET_MNEMONIC || 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about', + appUrl: process.env.DAPP_URL || 'https://allure-test-runner.vercel.app/e2e ' }), ); -const { expect } = test; - -test('Sign Data', async ({ wallet, app, widget }) => { - await allure.feature('Sign Data'); - await allure.story('Sign Text'); - await allure.tags('kit', 'wallet'); - await expect(widget.connectButtonText).toHaveText('Connect Wallet'); - await wallet.connectBy(await widget.connectUrl()); - await expect(widget.connectButtonText).not.toHaveText('Connect Wallet'); - await app.getByRole('button', { name: 'Sign Text' }).click(); - await wallet.signData(); - const signDataResultSelector = app.locator( - '.sign-data-tester > div:nth-child(6) > div.find-transaction-demo__json-label', - ); - await expect(signDataResultSelector).toHaveText('βœ… Verification Result'); + +let allureClient: AllureApiClient; + +test.beforeAll(async () => { + try { + // Π‘ΠΎΠ·Π΄Π°Π΅ΠΌ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ Allure + const config = createAllureConfig(); + + // Π‘ΠΎΠ·Π΄Π°Π΅ΠΌ ΠΊΠ»ΠΈΠ΅Π½Ρ‚ Allure + allureClient = new AllureApiClient(config); + + } catch (error) { + console.error('Error creating allure client:', error); + throw error; + } +}); + +async function runSignDataTest( + { wallet, app, widget }: { wallet: any; app: any; widget: any }, + testInfo: any) { + // ИзвлСкаСм allureId ΠΈΠ· названия тСста + const allureId = extractAllureId(testInfo.title); + + // УстанавливаСм Allure Π°Π½Π½ΠΎΡ‚Π°Ρ†ΠΈΠΈ + if (allureId) { + await allure.allureId(allureId); + await allure.owner('e.kurilenko'); + } + // Π˜Π½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·ΠΈΡ€ΡƒΠ΅ΠΌ ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Π΅ для Π΄Π°Π½Π½Ρ‹Ρ… тСст-кСйса + let precondition: string = ""; + let expectedResult: string = ""; + let isPositiveCase: boolean = true; + + if (allureId && allureClient) { + try { + const testCaseData = await getTestCaseData(allureClient, allureId); + precondition = testCaseData.precondition; + expectedResult = testCaseData.expectedResult; + isPositiveCase = testCaseData.isPositiveCase; + } catch (error) { + console.error('Error getting test case data:', error); + } + } else { + console.warn('AllureId not found in test title or client not available'); + } + await expect(widget.connectButtonText).toHaveText('Connect Wallet'); + await wallet.connectBy(await widget.connectUrl()); + await expect(widget.connectButtonText).toHaveText('UQC8…t2Iv'); + + + await app.locator('#signDataPrecondition').fill(precondition); + await app.locator('#signDataExpectedResult').fill(expectedResult); + await app.locator('#sign-data-button').click(); + //await app.getByRole('button', {name: 'Sign Data'}).click(); + + await wallet.signData(true, isPositiveCase); + + await expect(app.getByTestId('signDataValidation')).toHaveText('Validation Passed'); + } + +test('Sign text @allureId(1918)', async ({ wallet, app, widget }) => { + await runSignDataTest({ wallet, app, widget }, test.info()); +}); + +test('Sign cell @allureId(1920)', async ({ wallet, app, widget }) => { + await runSignDataTest({ wallet, app, widget }, test.info()); }); + +test('Sign binary @allureId(1919)', async ({ wallet, app, widget }) => { + await runSignDataTest({ wallet, app, widget }, test.info()); +}); \ No newline at end of file diff --git a/apps/demo-wallet/e2e/utils.ts b/apps/demo-wallet/e2e/utils.ts new file mode 100644 index 000000000..9c2747be7 --- /dev/null +++ b/apps/demo-wallet/e2e/utils.ts @@ -0,0 +1,218 @@ +interface TokenResponse { + access_token: string; + token_type: string; + expires_in: number; + scope: string; +} + +interface AllureConfig { + baseUrl: string; + apiToken: string; + projectId: number; +} + +/** + * ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅Ρ‚ JWT Ρ‚ΠΎΠΊΠ΅Π½ для Allure TestOps API + * @param config - ΠšΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡ Allure TestOps + * @returns Promise с JWT Ρ‚ΠΎΠΊΠ΅Π½ΠΎΠΌ + */ +export async function getAllureToken(config: AllureConfig): Promise { + const { baseUrl, apiToken } = config; + + const formData = new FormData(); + formData.append('grant_type', 'apitoken'); + formData.append('scope', 'openid'); + formData.append('token', apiToken); + + try { + const response = await fetch(`${baseUrl}/api/uaa/oauth/token`, { + method: 'POST', + headers: { + Accept: 'application/json', + }, + body: formData, + }); + + if (!response.ok) { + throw new Error(`Failed to get token: ${response.status} ${response.statusText}`); + } + + const tokenData: TokenResponse = await response.json(); + return tokenData.access_token; + } catch (error) { + throw new Error(`Error getting Allure token: ${error instanceof Error ? error.message : 'Unknown error'}`); + } +} + +/** + * ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅Ρ‚ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ тСст-кСйсС ΠΏΠΎ allureId + * @param config - ΠšΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡ Allure TestOps + * @param allureId - ID тСст-кСйса Π² Allure + * @returns Promise с Π΄Π°Π½Π½Ρ‹ΠΌΠΈ тСст-кСйса + */ +export async function getTestCaseByAllureId(config: AllureConfig, allureId: string): Promise { + const { baseUrl } = config; + const token = await getAllureToken(config); + + try { + const response = await fetch(`${baseUrl}/api/rs/testcase/allureId/${allureId}`, { + method: 'GET', + headers: { + Authorization: `Bearer ${token}`, + 'Content-Type': 'application/json', + Accept: 'application/json', + }, + }); + + if (!response.ok) { + throw new Error(`Failed to get test case: ${response.status} ${response.statusText}`); + } + + return await response.json(); + } catch (error) { + throw new Error(`Error getting test case: ${error instanceof Error ? error.message : 'Unknown error'}`); + } +} + +/** + * Π‘ΠΎΠ·Π΄Π°Π΅Ρ‚ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ Allure TestOps ΠΈΠ· ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Ρ… окруТСния + * @returns ΠšΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡ Allure TestOps + */ +export function createAllureConfig(): AllureConfig { + const baseUrl = process.env.ALLURE_BASE_URL || 'https://tontech.testops.cloud'; + const apiToken = process.env.ALLURE_API_TOKEN; + const projectId = parseInt(process.env.ALLURE_PROJECT_ID || '100'); + + if (!apiToken) { + throw new Error('ALLURE_API_TOKEN environment variable is required'); + } + + return { + baseUrl, + apiToken, + projectId, + }; +} + +/** + * Π£Ρ‚ΠΈΠ»ΠΈΡ‚Π° для Ρ€Π°Π±ΠΎΡ‚Ρ‹ с Allure TestOps API + */ +export class AllureApiClient { + private config: AllureConfig; + private token?: string; + private tokenExpiry?: number; + + constructor(config: AllureConfig) { + this.config = config; + } + + /** + * ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅Ρ‚ Π°ΠΊΡ‚ΡƒΠ°Π»ΡŒΠ½Ρ‹ΠΉ Ρ‚ΠΎΠΊΠ΅Π½ (с ΠΊΡΡˆΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅ΠΌ) + */ + private async getValidToken(): Promise { + const now = Date.now(); + + if (!this.token || !this.tokenExpiry || now >= this.tokenExpiry) { + this.token = await getAllureToken(this.config); + // Π’ΠΎΠΊΠ΅Π½ дСйствуСт 1 час, обновляСм Π·Π° 5 ΠΌΠΈΠ½ΡƒΡ‚ Π΄ΠΎ истСчСния + this.tokenExpiry = now + 55 * 60 * 1000; + } + + return this.token; + } + + /** + * ВыполняСт Π°Π²Ρ‚ΠΎΡ€ΠΈΠ·ΠΎΠ²Π°Π½Π½Ρ‹ΠΉ запрос ΠΊ Allure API + */ + private async makeRequest(endpoint: string, options: RequestInit = {}): Promise { + const token = await this.getValidToken(); + + const response = await fetch(`${this.config.baseUrl}${endpoint}`, { + ...options, + headers: { + Authorization: `Bearer ${token}`, + 'Content-Type': 'application/json', + Accept: 'application/json', + ...options.headers, + }, + }); + + if (!response.ok) { + throw new Error(`API request failed: ${response.status} ${response.statusText}`); + } + + return response; + } + + /** + * ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅Ρ‚ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ тСст-кСйсС ΠΏΠΎ allureId + */ + async getTestCase(allureId: string): Promise { + const response = await this.makeRequest(`/api/rs/testcase/allureId/${allureId}`); + return await response.json(); + } + + /** + * ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅Ρ‚ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π΅ + */ + async getProject(): Promise { + const response = await this.makeRequest(`/api/rs/project/${this.config.projectId}`); + return await response.json(); + } + + /** + * ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅Ρ‚ список тСст-ΠΏΠ»Π°Π½ΠΎΠ² + */ + async getTestPlans(): Promise { + const response = await this.makeRequest(`/api/rs/project/${this.config.projectId}/testplan`); + return await response.json(); + } + + /** + * ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅Ρ‚ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ тСст-кСйсС ΠΏΠΎ ID + * @param id - ID тСст-кСйса + * @returns Promise с Π΄Π°Π½Π½Ρ‹ΠΌΠΈ тСст-кСйса + */ + async getTestCaseById(id: string): Promise { + const response = await this.makeRequest(`/api/testcase/${id}`); + return await response.json(); + } +} + +/** + * Π˜Π·Π²Π»Π΅ΠΊΠ°Π΅Ρ‚ allureId ΠΈΠ· названия тСста + * @param testTitle - Π½Π°Π·Π²Π°Π½ΠΈΠ΅ тСста + * @returns allureId ΠΈΠ»ΠΈ null Ссли Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½ + */ +export function extractAllureId(testTitle: string): string | null { + const match = testTitle.match(/@allureId\((\d+)\)/); + return match ? match[1] : null; +} + +/** + * ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅Ρ‚ Π΄Π°Π½Π½Ρ‹Π΅ тСст-кСйса ΠΈ ΠΈΠ·Π²Π»Π΅ΠΊΠ°Π΅Ρ‚ precondition ΠΈ expectedResult + * @param allureClient - ΠΊΠ»ΠΈΠ΅Π½Ρ‚ Allure API + * @param allureId - ID тСст-кСйса + * @returns Promise с ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ΠΎΠΌ содСрТащим preconditions ΠΈ expectedResult + */ +export async function getTestCaseData( + allureClient: AllureApiClient, + allureId: string, +): Promise<{ + precondition: string; + expectedResult: string; + isPositiveCase: boolean; +}> { + try { + const testCaseData = await allureClient.getTestCaseById(allureId); + const isPositiveCase = !String(testCaseData.name).toLowerCase().includes('error'); + + return { + isPositiveCase, + ...testCaseData, + }; + } catch (error) { + console.error('Error getting test case data:', error); + throw error; + } +} diff --git a/apps/demo-wallet/src/components/ConnectRequestModal.tsx b/apps/demo-wallet/src/components/ConnectRequestModal.tsx index ea22b9897..1d075abfb 100644 --- a/apps/demo-wallet/src/components/ConnectRequestModal.tsx +++ b/apps/demo-wallet/src/components/ConnectRequestModal.tsx @@ -195,11 +195,17 @@ export const ConnectRequestModal: React.FC = ({ {/* Action Buttons */}
-
-
{children}
); diff --git a/apps/demo-wallet/src/components/TransactionRequestModal.tsx b/apps/demo-wallet/src/components/TransactionRequestModal.tsx index 69bcc30e6..999c6273e 100644 --- a/apps/demo-wallet/src/components/TransactionRequestModal.tsx +++ b/apps/demo-wallet/src/components/TransactionRequestModal.tsx @@ -171,7 +171,13 @@ export const TransactionRequestModal: React.FC = ( {/* Action Buttons */}
- diff --git a/package.json b/package.json index 594a36353..a519ebc4b 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "devDependencies": { "@changesets/cli": "^2.29.6", "@ton/toolchain": "github:the-ton-tech/toolchain#v1.5.0", + "dotenv": "^17.2.2", "eslint": "^9.33.0", "globals": "^16.3.0", "rimraf": "^6.0.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index abd9ae38f..adae7c0e8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,9 @@ importers: '@ton/toolchain': specifier: github:the-ton-tech/toolchain#v1.5.0 version: https://codeload.github.com/the-ton-tech/toolchain/tar.gz/31376da778155bd0984d68abf2a46dce417bacb8(jest@29.7.0(@types/node@24.3.0)(node-notifier@10.0.1))(jiti@2.5.1)(typescript@5.8.3) + dotenv: + specifier: ^17.2.2 + version: 17.2.2 eslint: specifier: ^9.33.0 version: 9.33.0(jiti@2.5.1)