Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add lastFailed config option #973

Merged
merged 12 commits into from
Jul 29, 2024
Merged

feat: add lastFailed config option #973

merged 12 commits into from
Jul 29, 2024

Conversation

Kabir-Ivan
Copy link
Collaborator

@Kabir-Ivan Kabir-Ivan commented Jul 16, 2024

Make it possible to run only failed tests by adding --run-failed option in CLI.

Copy link
Member

@KuznetsovRoman KuznetsovRoman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Otherwise, /ok
But let's see what the others say

src/testplane.ts Outdated Show resolved Hide resolved
src/testplane.ts Outdated Show resolved Hide resolved
src/testplane.ts Outdated Show resolved Hide resolved
src/testplane.ts Outdated Show resolved Hide resolved
src/testplane.ts Outdated Show resolved Hide resolved
src/testplane.ts Outdated Show resolved Hide resolved
src/testplane.ts Outdated Show resolved Hide resolved
src/testplane.ts Outdated Show resolved Hide resolved
src/testplane.ts Outdated Show resolved Hide resolved
src/testplane.ts Outdated Show resolved Hide resolved
docs/config.md Outdated
@@ -593,6 +594,9 @@ export const {
}
```

### failedTestsPath
Testplane will save a list of all failed tests to `failedTestsPath`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to specify the default value here.

src/testplane.ts Outdated Show resolved Hide resolved
src/cli/index.js Outdated
@@ -58,6 +58,7 @@ exports.run = (opts = {}) => {
"--update-refs",
'update screenshot references or gather them if they do not exist ("assertView" command)',
)
.option("--run-failed", "only run tests that failed during the last run")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it may be named something like --run-last-failed, that would sound more clear

src/cli/index.js Outdated
@@ -78,6 +79,7 @@ exports.run = (opts = {}) => {
set: sets,
grep,
updateRefs,
runFailed,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, perhaps there should be an option in config and env variable for that as well? I'm not sure though. Thinking about using this in CI, it would be convenient to have separate config for CI to run only failed tests by default or specify it by using env variable. Discussble

src/testplane.ts Outdated
}

protected async _saveFailed(): Promise<void> {
const dirname = path.dirname(this._config.failedTestsPath);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking about using this in CI, I think it would be convenient to be able to fetch this file from remote URL...

But that'd require to have two separate options for input and output. Generally, let's discuss with the team if we need run options like grep and files to be in config?
This would make possible doing so:

export default {
    grep: /some-test/,
    lastFailed: {
        only: true,
        output: '.testplane/file.json',
        input: 'https://ci-artifacts.com/.../file.json',
    }, // or just true/false if default input/output are sufficient

    // other config options
}

src/testplane.ts Outdated
});

this.on(MasterEvents.AFTER_TESTS_READ, testCollection => {
if (previousRunFailedTests.length) {
Copy link
Member

@shadowusr shadowusr Jul 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if this is the best way to do this. In src/test-reader/test-parser we have this code:

if (grep) {
    treeBuilder.addTestFilter(test => grep.test(test.fullTitle()));
}

We probably can implement this the same way, by adding a filter function.

The goal is to keep main file src/testplane.ts short and clean.

src/testplane.ts Outdated
previousRunFailedTests.forEach(({ fullTitle, browserId }) => {
testCollection.enableTest(fullTitle, browserId);
});
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, if run-failed option was passed, but no failed tests file was found, should we print a warning about it, what do you think?

Copy link
Member

@DudaGod DudaGod left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are some questions

docs/config.md Outdated Show resolved Hide resolved
docs/config.md Outdated Show resolved Hide resolved
docs/config.md Outdated Show resolved Hide resolved
docs/config.md Outdated Show resolved Hide resolved
src/config/types.ts Show resolved Hide resolved
src/testplane.ts Outdated Show resolved Hide resolved
test/src/testplane.js Outdated Show resolved Hide resolved
test/src/test-reader/test-parser.js Outdated Show resolved Hide resolved
test/src/testplane.js Outdated Show resolved Hide resolved
test/src/testplane.js Outdated Show resolved Hide resolved
Copy link
Member

@DudaGod DudaGod left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Everything is cool, but I suggest using Map for matching and add browserVersion to failedTests, because we can use few versions of one browser.

this.#failedTests = await fs.readJSON(config.lastFailed.input);
} catch {
logger.warn(
`Could not read failed tests data at ${this._config.lastFailed.input}. Running all tests instead`,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To my mind we should write an error here, because of which we couldn't read the file.

@@ -66,6 +70,16 @@ class TestParser extends EventEmitter {
const esmDecorator = f => f + `?rand=${rand}`;
await readFiles(files, { esmDecorator, config: mochaOpts, eventBus });

if (config.lastFailed.only) {
try {
this.#failedTests = await fs.readJSON(config.lastFailed.input);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we store tests like object with uniq id as string? It gives ability to match tests by key faster then iterate by array.

Moreover we should save browserVersion because we have pluign like https://github.com/gemini-testing/hermione-browser-version-changer/tree/master which gives ability to run tests in different browser versions. More info here - https://nda.ya.ru/t/ctpevAUg777hZX.

So to my mind we can use Map like this:

#failedTests: Map<string, {fullTitle: string, browserId: string, browserVersion?: string}>;
// ...

this.#failedTests = new Map();

for (test of await fs.readJSON(config.lastFailed.input)) {
  const id = getShortMD5(`${test.fullTitle}${test.browserId}${test.browserVersion}`);
  this.#failedTests.set(id, _.pick(test, ['fullTitle', 'browserId', 'browserVersion']);
});

const failedStringified = this.#failedTests.map(data => JSON.stringify(data));
treeBuilder.addTestFilter(test =>
failedStringified.includes(
JSON.stringify({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like solution with iterate through all tests in order to find desired one. I described the above solution with Map, which should be much faster. What do you think?

It will also allow us to get rid of json.strigify here.

Copy link
Member

@DudaGod DudaGod left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The source is ok, but I don't see a lot of tests on this new functionality. Let's add them.

: config.lastFailed.input.split(",").map(v => v.trim());
for (const inputPath of inputPaths) {
for (const test of await fs.readJSON(inputPath)) {
this.#failedTests.add(getShortMD5(`${test.fullTitle}${test.browserId}${test.browserVersion}`));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's maybe put it in a separate function? Like this:

const getFailedTestId = (test) => getShortMD5(`${test.fullTitle}${test.browserId}${test.browserVersion}`);


Config.read.returns(readConfig);

assert.throws(() => createConfig(), Error, '"lastFailed.input" must be a string');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error message was changed

});
});

describe("input", () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see tests:

  • string without .json at the end -> throw
  • array without string + .json at the end -> throw
  • string with .json -> ok
  • array with .json -> ok


Config.read.returns(readConfig);

assert.throws(() => createConfig(), Error, '"lastFailed.output" must be a string');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error message was changed

});
});

describe("output", () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see tests:

  • string without .json at the end -> throw
  • string with .json -> ok

@@ -14,6 +14,7 @@ const proxyquire = require("proxyquire").noCallThru();
const path = require("path");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where are the tests for this module? There should be quite a lot of them here since the main code is in this file.

In loadFiles (public) we can check that:

  • config.lastFailed.only not set -> do not read json files
  • config.lastFailed.only not set -> do not call addTestFilter (parse should be called after loadFiles)
  • config.lastFailed.only set -> read json from one file (string)
  • config.lastFailed.only set -> read json from one file (string with commas, case with env)
  • config.lastFailed.only set -> read json from few files (array of strings)
  • and other tests

Method parse must be tested as well.


return runTestplane().then(success => assert.isFalse(success));
});

it("should halt if there were some errors", () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why you remove this test?

@@ -467,16 +497,20 @@ describe("testplane", () => {
it("all runner events with passed event data", () => {
const runner = mkNodejsEnvRunner_();
const testplane = mkTestplane_();
const results = {
fullTitle: () => "Title",
browserId: "chrome",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

then maybe add browserVersion as well?

@@ -761,8 +795,13 @@ describe("testplane", () => {
it('should return "true" after some test fail', () => {
const testplane = mkTestplane_();

const results = {
fullTitle: () => "Title",
browserId: "chrome",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

then maybe add browserVersion as well?

@Kabir-Ivan Kabir-Ivan requested a review from DudaGod July 25, 2024 12:52
Copy link
Member

@DudaGod DudaGod left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest fix few tests, but otherwise everything is ok!

this.#buildInstructions = new InstructionsList();
}

getFailedTestId = test => getShortMD5(`${test.fullTitle}${test.browserId}${test.browserVersion}`);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be a helper, because it doesn't use class methods


await parse_({ config }, config);

assert.calledOnce(TreeBuilder.prototype.addTestFilter);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can get callback using - TreeBuilder.prototype.addTestFilter.lastCall.args[0] and call it with passed test. And then check if it returns true or false. You can see examples of testing grep in this file.

@Kabir-Ivan Kabir-Ivan merged commit 36aa516 into master Jul 29, 2024
2 checks passed
@Kabir-Ivan Kabir-Ivan deleted the add-run-failed branch July 29, 2024 11:06
@Kabir-Ivan Kabir-Ivan changed the title feat: add --run-failed option feat: add lastFailed config option Aug 15, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants