[microsoft/playwright]如何使用 allure-playwright 附加屏幕录制?

2024-06-25 603 views
7

有没有办法在 allure-playwright-reporter 中对每个测试进行屏幕录制。我可以为每个测试捕获屏幕截图,但也希望在仪表板上进行屏幕录制。如何才能实现这一点?

这是我的playwright.config.js,仅供参考

/** @type {import('@playwright/test').PlaywrightTestConfig} */
const config = {
    use : {
        launchOptions: {
            devtools: false
        },
        contextOptions:{
            recordVideo: {
                dir: "../test-results/videos/"
            }
        },
        testDir: './tests',
        headless: false,
        viewport: { width: 1280, height: 900 },
        channel: "chrome",
        screenshot: "on",
        video: "on",
        trace: "on"
    },
    testMatch: /traderTest.js/,
    retries: 0,
    reporter: [["list"], ["json", { outputFile: "test-results.json"}] ,['experimental-allure-playwright']]
};
module.exports = config;

仪表板如下所示

截图 2022-05-13 下午 3 点 09 分 36 秒

正如你在测试主体下看到的,我有跟踪和屏幕截图,但我还需要屏幕录制

回答

7

@sinhaman1909 您能否提供有关您的环境和依赖项版本的更多信息。

只需创建一个具有配置的新应用程序即可。

我的环境:playwright:1.22.0 playwright/test:1.22.0 allure-playwright:2.0.0-beta.16

我的配置文件:

// playwright.config.ts
import type { PlaywrightTestConfig } from '@playwright/test';
import { devices } from '@playwright/test';

/**
 * Read environment variables from file.
 * https://github.com/motdotla/dotenv
 */
// require('dotenv').config();

/**
 * See https://playwright.dev/docs/test-configuration.
 */
const config: PlaywrightTestConfig = {
  testDir: './tests',
  /* Maximum time one test can run for. */
  timeout: 30 * 1000,
  expect: {
    /**
     * Maximum time expect() should wait for the condition to be met.
     * For example in `await expect(locator).toHaveText();`
     */
    timeout: 5000
  },
  /* 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'],
    ['allure-playwright'] // you use "experimental-allure-playwright" package
  ],
  /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
  use: {
    /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
    actionTimeout: 0,
    /* Base URL to use in actions like `await page.goto('/')`. */
    // baseURL: 'http://localhost:3000',

    /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
    trace: 'on',
    contextOptions: {
      recordVideo: {
        dir: './test-results/videos/'
      }
    },
    headless: false,
    video: 'on',
  },

  /* Configure projects for major browsers */
  projects: [
    {
      name: 'chromium',
      use: {
        ...devices['Desktop Chrome'],
      },
    },
  ],
};

export default config;

我的魅力报告:

图像

如您所见,视频已正确附加。您可以尝试更新experimental-allure-playwrightallure-playwright

3

我正在使用 playwright 编写 node.js - javascript

我的Package.json

{
    "devDependencies": {
        "@playwright/test": "^1.21.1",
        "allure-commandline": "^2.17.2",
        "chai": "^4.3.6",
        "experimental-allure-playwright": "^0.0.3",
        "ffmpeg-static": "^5.0.0",
        "mocha": "^10.0.0",
        "playwright": "1.21.1",
        "playwright-video": "^2.4.0"
    },
    "dependencies": {
        "playwright-core": "^1.21.1",
        "playwright-mocha": "^2.0.2"
    },
    "scripts": {
        "allure:generate": "npx allure generate ./allure-results --clean",
        "allure:open": "npx allure open ./allure-report",
        "allure:serve": "npx allure serve",
        "test": "npx playwright test || :",
        "posttest": "npm run allure:generate"
      }
}

我会先尝试一下,然后告诉你。谢谢

7

@vitalics

我尝试使用上述配置,但在 Allure 仪表板中看不到屏幕录制片段。

我附上了截图

截图于 2022-05-15 上午 1 点 44 分 30 秒

我只是跳过了配置中的这部分,因为我不想在不同的浏览器上运行我的项目,其余的都与你的相同。

/* Configure projects for major browsers */
  projects: [
    {
      name: 'chromium',
      use: {
        ...devices['Desktop Chrome'],
      },
    },
  ],

我还从 package.json 中删除了所有无用的依赖项,例如 mocha 和 chai,并将 experiments-allure-playwright 更新为 allure-playwright

8

你的测试是什么样的?

playwright-video是不需要的——它是第三方的,而且我建议放弃playwright-core依赖,playwright因为你@playwright/test已经有了,这就足够了。

contextOptions无需通过海关,video: "on"在那里就足够了。

5

我放弃了依赖playwright-core playwright-videoplaywright但仍然无法在仪表板中看到视频。

我也contextOptions按照你说的删除了。

更新了 package.json

{
    "devDependencies": {
        "@playwright/test": "^1.22.0",
        "allure-commandline": "^2.17.2",
        "allure-playwright": "^2.0.0-beta.1"
    },
    "scripts": {
        "allure:generate": "npx allure generate ./allure-results --clean",
        "allure:open": "npx allure open ./allure-report",
        "allure:serve": "npx allure serve",
        "test": "npx playwright test || :",
        "posttest": "npm run allure:generate"
    }
}

我的测试如下:

const { test,Page, expect } = require('@playwright/test');

test.describe("Trader Test", () => {
    let page = Page;

    //Setting the browser context and initializing page
    test.beforeAll(async ( { browser } ) => {
        const context = await browser.newContext();
        page = await context.newPage();
    })

    //Test to Connect to the login page
    test('Connect to login page', async() => {
        await page.goto("http://localhost:3000/trader-desktop/login");
        await expect(page.locator('#login-container > div:nth-child(1) > span')).toHaveText('Welcome to Ninja App !');
    });

    //Token Authentication using local Storage
    test('Token Authentication', async() => {
        const fs = require('fs');
        const localStorage = fs.readFileSync('../localStorage.json', 'utf8');
        const deserializedStorage = JSON.parse(localStorage);
        await page.evaluate(deserializedStorage => {
            for (const key in deserializedStorage) {
                if (key === "realms") {
                    localStorage.setItem(key, JSON.stringify(deserializedStorage[key]));
                }
                else {
                    localStorage.setItem(key, deserializedStorage[key]);
                }
            }
        }, deserializedStorage);
        await page.goto("http://localhost:3000/trader-desktop/account-selection");
        await expect(page.locator('//*[@id="root"]/div[2]/div[1]/div[2]/div/span')).toHaveText('Login Successful, Select a Business to Begin...');
    });

    //Clicking on Suriya Traders
    test('Navigate to Suriya Traders', async() => {

        await page.click("text = Suriya traders");
        await expect(page.locator('//*[@id="root"]/div[2]/div/div[2]/div[1]/div/div/button[1]')).toHaveText('Add Party');
    });

    //Clicking on Transactions
    test('Navigate to Transactions page', async() => {
        await page.click("text = Transactions");
        await expect(page.locator('//*[@id="root"]/div[2]/div/div[1]/div/span')).toHaveText('Transactions');
    })

    //Returning to Dashboard
    test('Navigate to Dashboard', async() => {
        await page.click("text = Dashboard");
        await expect(page.locator('//*[@id="root"]/div[2]/div/div[2]/div[3]/div/div/span')).toContainText('Cash Flow Summary');
    })

    //Clicking on Logout
    test('Logout', async() => {
        await page.click('text = Logout');
        await expect(page.locator('#login-container > div:nth-child(1) > span')).toHaveText('Welcome!');
    })

    //Negative Authentication test
    test('Failed login', async() => {
        var inputBoxes = 6;
        await page.locator('input:visible').fill('843534534');
        await page.click("text = Get OTP");
        //Fill input boxes with '0'
        for (var i = 0; i < inputBoxes; i++) {
            await page.locator('[aria-label="Digit 6"]').fill('0');
        }
        await page.click("text = Login");
        await expect(page.locator('text = Request failed with status code 400')).not.toHaveText('Request failed with status code 400');
    }) 

})

我的 playwright.config.js

/** @type {import('@playwright/test').PlaywrightTestConfig} */
const config = {
    use : {
        actionTimeout: 0,
        launchOptions: {
            devtools: false
        },
        testDir: './tests',
        testMatch: /traderTest.js/,
        headless: false,
        viewport: { width: 1280, height: 1080 },
        channel: "chrome",
        screenshot: "on",
        video: {
            mode: "on",
            size: {
                width: "1260",
                height: 1080
            }
        },
        trace: "on"
    },
    reporter: [["list"], ["json", { outputFile: "test-results.json"}] ,['allure-playwright']],
    projects: [
        {
          name: 'Trader',
          testMatch: /.*traderTest.js/,
          retries: 0,
        }
    ]
};
module.exports = config;
2

你看到文件夹下的视频了吗test-results?我感觉因为你自己创建了上下文,并且没有关闭它,所以它无法正常工作。请尝试以下操作:

    let page = Page;
    let context = BrowserContext;

    //Setting the browser context and initializing page
    test.beforeAll(async ( { browser } ) => {
        context = await browser.newContext();
        page = await context.newPage();
    })

    test.afterAll(async ({ }) => {
        await context.close();
    });
4

不,我看不到文件夹下的视频test-results。如果我contextOptions: { recordVideo: { dir: './test-results/videos/' } }, 在配置中提到,那么我可以看到test-results文件夹中的视频,但仍然看不到仪表板上的视频。我也尝试关闭上下文。仍然不起作用

4

它不起作用的原因是您手动创建了上下文。尝试不要手动创建它,从装置中获取它并使用 describe.serial(串行模式):https://playwright.dev/docs/next/test-retries#serial-mode

const { test,Page, expect } = require('@playwright/test');

test.describe.serial("Trader Test", () => {

    //Test to Connect to the login page
    test('Connect to login page', async({page}) => {
        await page.goto("http://localhost:3000/trader-desktop/login");
        await expect(page.locator('#login-container > div:nth-child(1) > span')).toHaveText('Welcome to Ninja App !');
    });
    // .....
})
7

我按照你说的做了,但现在我的第二个测试用例Token Authentication失败了。只有第一个测试通过了。不过现在我可以看到第一个测试用例的视频。这是错误:

1) [Trader] › traderTest.js:18:5 › Trader Test › Token Authentication ============================

    page.evaluate: DOMException: Failed to read the 'localStorage' property from 'Window': Access is denied for this document.

      20 |         const localStorage = fs.readFileSync('../localStorage.json', 'utf8');
      21 |         const deserializedStorage = JSON.parse(localStorage);
    > 22 |         await page.evaluate(deserializedStorage => {
         |                    ^
      23 |             for (const key in deserializedStorage) {
      24 |                 if (key === "realms") {
      25 |                     window.localStorage.setItem(key, JSON.stringify(deserializedStorage[key]));

        at eval (eval at evaluate (:178:30), <anonymous>:7:28)
        at UtilityScript.evaluate (<anonymous>:180:17)
        at UtilityScript.<anonymous> (<anonymous>:1:44)
        at /Users/nc23625-aman/Desktop/Playwright2/test/traderTest.js:22:20

我的测试现在看起来是这样的:

const { test,Page, expect } = require('@playwright/test');

test.describe.serial("Trader Test", () => {
    let page = Page;

    //Setting the browser context and initializing page

    //Test to Connect to the login page
    test('Connect to login page', async({ page }) => {
        await page.goto("http://localhost:3000/trader-desktop/login");
        await expect(page.locator('#login-container > div:nth-child(1) > span')).toHaveText('Welcome to Ninja App !');
    });

    //Token Authentication using local Storage
    test('Token Authentication', async({ page }) => {
        const fs = require('fs');
        const localStorage = fs.readFileSync('../localStorage.json', 'utf8');
        const deserializedStorage = JSON.parse(localStorage);
        await page.evaluate(deserializedStorage => {
            for (const key in deserializedStorage) {
                if (key === "realms") {
                    localStorage.setItem(key, JSON.stringify(deserializedStorage[key]));
                }
                else {
                    localStorage.setItem(key, deserializedStorage[key]);
                }
            }
        }, deserializedStorage);
        await page.goto("http://localhost:3000/trader-desktop/account-selection");
        await expect(page.locator('//*[@id="root"]/div[2]/div[1]/div[2]/div/span')).toHaveText('Login Successful, Select a Business to Begin...');
    });

    //Clicking on Suriya Traders
    test('Navigate to Suriya Traders', async({ page }) => {

        await page.click("text = Suriya traders");
        await expect(page.locator('//*[@id="root"]/div[2]/div/div[2]/div[1]/div/div/button[1]')).toHaveText('Add Party');
    });

    //Clicking on Transactions
    test('Navigate to Transactions page', async({ page }) => {
        await page.click("text = Transactions");
        await expect(page.locator('//*[@id="root"]/div[2]/div/div[1]/div/span')).toHaveText('Transactions');
    })

    //Returning to Dashboard
    test('Navigate to Dashboard', async({ page }) => {
        await page.click("text = Dashboard");
        await expect(page.locator('//*[@id="root"]/div[2]/div/div[2]/div[3]/div/div/span')).toContainText('Cash Flow Summary');
    })

    //Clicking on Logout
    test('Logout', async({ page }) => {
        await page.click('text = Logout');
        await expect(page.locator('#login-container > div:nth-child(1) > span')).toHaveText('Welcome to Ninja App !');
    })

    //Negative Authentication test
    test('Failed login', async({ page }) => {
        var inputBoxes = 6;
        await page.locator('input:visible').fill('8851002432');
        await page.click("text = Get OTP");
        //Fill input boxes with '0'
        for (var i = 0; i < inputBoxes; i++) {
            await page.locator('[aria-label="Digit 6"]').fill('0');
        }
        await page.click("text = Login");
        await expect(page.locator('text = Request failed with status code 400')).not.toHaveText('Request failed with status code 400');
    }) 
})
6

手动创建上下文时,某些 playwright 配置值不受尊重,因为它们仅适用于默认上下文。如果您仍想拥有视频并附加它,您可以执行以下操作:

// example.spec.ts

import { test, Page } from '@playwright/test';

test.describe.configure({ mode: 'serial' });

let page: Page;

test.beforeAll(async ({ browser }, testInfo) => {
  page = await browser.newPage({
    recordVideo: {
      dir: testInfo.outputPath('videos'),
    }
  });
  await page.goto('https://github.com/login');
});

test.afterAll(async ({}, testInfo) => {
  const videoPath = testInfo.outputPath('my-video.webm');
  await Promise.all([
    page.video().saveAs(videoPath),
    page.close()
  ]);
  testInfo.attachments.push({
    name: 'video',
    path: videoPath,
    contentType: 'video/webm'
  });
});

test('first test', async () => {
  await page.fill('input[name="login"]', 'user');
  await page.fill('input[name="password"]', 'password');
});

test('second test', async () => {
  await page.fill('input[name="login"]', 'user');
  await page.fill('input[name="password"]', 'password');
});

我刚刚测试过它并且它对我有用。