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

Support 0-level assertion priorities on TestRun page #863

Merged
merged 20 commits into from
Feb 6, 2024
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
69245f8
Update client/resources
howard-e Dec 7, 2023
619a5fe
Update graphql-schema.js and testsResolver
howard-e Dec 7, 2023
0c099f2
Update assertionResultsResolver.js
howard-e Dec 7, 2023
51ae9a5
Update import-tests script to include assertion.assertionExceptions (…
howard-e Dec 7, 2023
7c464db
Update getMetrics and queries to be aware of 'excluded' assertions fo…
howard-e Dec 7, 2023
bde9219
Update import branch for testing
howard-e Dec 7, 2023
c7d57fa
Update import branch for testing
howard-e Dec 7, 2023
5c4ec6a
Remove 'exclude' property check reliance on frontend
howard-e Dec 13, 2023
7de158b
Revert graphql fragment usage
howard-e Dec 14, 2023
3cbe58a
Fix missing renderableContent in anon viewer query
howard-e Dec 14, 2023
635cf65
Rename instances of Command.settings to Command.atOperatingMode in gr…
howard-e Dec 14, 2023
318b7f4
Include migration to update existing test plan versions using the v2 …
howard-e Jan 22, 2024
0d5d165
Merge branch 'main' into support-0-level-priority
stalgiag Jan 22, 2024
a26a572
Move atOperatingMode setting to TestsService, getTests
stalgiag Jan 22, 2024
62ecce2
Missing comma
stalgiag Jan 22, 2024
45d3ed6
Merge branch 'main' into support-0-level-priority
howard-e Jan 30, 2024
af9a5b0
Update comments with reasoning for filtering out assertions that can …
howard-e Feb 6, 2024
3d1fbcb
Update convertAssertionPriority usage
howard-e Feb 6, 2024
942223d
Revert test branch being used back to master
howard-e Feb 6, 2024
bba8ad4
Break out in-line filter into function sufficiently support readabili…
howard-e Feb 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 14 additions & 6 deletions client/components/TestRenderer/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ const TestRenderer = ({
}

for (let i = 0; i < scenarioResults.length; i++) {
const {
let {
output,
assertionResults,
unexpectedBehaviors,
Expand All @@ -259,15 +259,23 @@ const TestRenderer = ({
if (output) commands[i].atOutput.value = output;
commands[i].atOutput.highlightRequired = highlightRequired;

// Required because assertionResults can now be returned without an id if there is a 0-priority exception
// applied
assertionResults = assertionResults.filter(el => !!el.id);

for (let j = 0; j < assertionResults.length; j++) {
const assertionResult = assertionResults[j];
const { highlightRequired } = assertionResult;
const { passed, highlightRequired, assertion } =
assertionResults[j];

commands[i].assertions[j].result = assertionResult.passed
let assertionForCommandIndex = commands[i].assertions.findIndex(
({ description }) => description === assertion?.text
);
commands[i].assertions[assertionForCommandIndex].result = passed
? 'pass'
: 'fail';

commands[i].assertions[j].highlightRequired = highlightRequired;
commands[i].assertions[
assertionForCommandIndex
].highlightRequired = highlightRequired;
}

if (unexpectedBehaviors && unexpectedBehaviors.length) {
Expand Down
19 changes: 13 additions & 6 deletions client/components/TestRun/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -402,9 +402,12 @@ const TestRun = () => {

// process assertion results
for (let j = 0; j < assertions.length; j++) {
const { result, highlightRequired } = assertions[j];
const { description, result, highlightRequired } =
assertions[j];
const assertionResult = {
...scenarioResult.assertionResults[j],
...scenarioResult.assertionResults.find(
({ assertion: { text } }) => text === description
),
passed: result === 'pass'
};
assertionResults.push(
Expand Down Expand Up @@ -656,10 +659,14 @@ const TestRun = () => {
id,
output: output,
unexpectedBehaviors: unexpectedBehaviors,
assertionResults: assertionResults.map(({ id, passed }) => ({
id,
passed
}))
assertionResults: assertionResults
// All assertions are always being passed from the TestRenderer results, but in the instance where
// there is a 0-priority exception, and id won't be provided and that cannot be saved
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this taking advantage of a side effect or is this the strategy used for communicating that an assertion is excluded? It seems easy to misunderstand or forget. I would expect them to be passed prefiltered but I might be missing something.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's been some time now, so I'll have to look back into why exactly it was done that way. But from what I can remember, the TestRenderer still technically requires the index of that excluded assertion to properly determine which assertions are which, but at some point. It definitely had to be removed before saving and those excluded ones had no ids. That was a part of fulfilling this request with 5c4ec6a.

Is this taking advantage of a side effect or is this the strategy used for communicating that an assertion is excluded?

So seems to be a bit of both.

This point in the code looks like the safest place to filter them out, as the single point before being passed to the server/db. The comment could be much clearer as well.

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh okay. The background is helpful. I'll leave it to you as to whether you want to change the comment or address it any other way.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in af9a5b0

.filter(el => !!el.id)
.map(({ id, passed }) => ({
id,
passed
}))
})
);

Expand Down
3 changes: 3 additions & 0 deletions client/components/TestRun/queries.js
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,7 @@ export const FIND_OR_CREATE_TEST_RESULT_MUTATION = gql`
id
title
phase
updatedAt
gitSha
testPageUrl
testPlan {
Expand Down Expand Up @@ -807,6 +808,7 @@ export const SAVE_TEST_RESULT_MUTATION = gql`
id
title
phase
updatedAt
gitSha
testPageUrl
testPlan {
Expand Down Expand Up @@ -1091,6 +1093,7 @@ export const SUBMIT_TEST_RESULT_MUTATION = gql`
id
title
phase
updatedAt
gitSha
testPageUrl
testPlan {
Expand Down
59 changes: 55 additions & 4 deletions client/resources/aria-at-test-io-format.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,8 @@ class CommandsInput {
commands.push(command);
commandsAndSettings.push({
command,
commandId,
presentationNumber: Number(presentationNumber),
settings: _atMode,
settingsText: assistiveTech.settings?.[_atMode]?.screenText || 'default mode active',
settingsInstructions: assistiveTech.settings?.[_atMode]?.instructions || [
Expand Down Expand Up @@ -767,6 +769,9 @@ class BehaviorInput {

const { commandsAndSettings } = commandsInput.getCommands({ task: json.task }, mode);

// Use to determine assertionExceptions
const commandsInfo = json.commandsInfo?.[at.key];

return new BehaviorInput({
behavior: {
description: titleInput.title(),
Expand All @@ -778,7 +783,31 @@ class BehaviorInput {
setupScriptDescription: json.setup_script_description,
setupTestPage: json.setupTestPage,
assertionResponseQuestion: json.assertionResponseQuestion,
commands: commandsAndSettings,
commands: commandsAndSettings.map(cs => {
const foundCommandInfo = commandsInfo?.find(
c =>
cs.commandId === c.command &&
cs.presentationNumber === c.presentationNumber &&
cs.settings === c.settings
);
if (!foundCommandInfo || !foundCommandInfo.assertionExceptions) return cs;

// Only works for v2
let assertionExceptions = json.output_assertions.map(each => each.assertionId);
foundCommandInfo.assertionExceptions.split(' ').forEach(each => {
let [priority, assertionId] = each.split(':');
const index = assertionExceptions.findIndex(each => each === assertionId);
Copy link
Contributor

Choose a reason for hiding this comment

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

Array.prototype.indexOf would be a bit more readable here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed


priority = Number(priority);
assertionExceptions[index] = priority;
Comment on lines +801 to +802
Copy link
Contributor

Choose a reason for hiding this comment

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

Under the assumption that "fewer mutable bindings is better", these statements could be combined to allow swapping the earlier let for a const.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sounds good

});
// Preserve default priority or update with exception
assertionExceptions = assertionExceptions.map((each, index) =>
isNaN(each) ? json.output_assertions[index].priority : each
Copy link
Contributor

Choose a reason for hiding this comment

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

Aren't the members of assertions string values? Returning each from a filter predicate suggests otherwise.

);

return { ...cs, assertionExceptions };
}),
assertions: (json.output_assertions ? json.output_assertions : []).map(assertion => {
// Tuple array [ priorityNumber, assertionText ]
if (Array.isArray(assertion)) {
Expand All @@ -788,7 +817,7 @@ class BehaviorInput {
};
}

// { assertionId, priority, assertionStatement, assertionPhrase, refIds, commandInfo, tokenizedAssertionStatements }
// { assertionId, priority, assertionStatement, assertionPhrase, refIds, tokenizedAssertionStatements }
return {
priority: assertion.priority,
assertion:
Expand All @@ -815,7 +844,7 @@ class BehaviorInput {
* @param {UnexpectedInput} data.unexpectedInput
*/
static fromCollectedTestCommandsKeysUnexpected(
{ info, target, instructions, assertions },
{ info, target, instructions, assertions, commands },
{ commandsInput, keysInput, unexpectedInput }
) {
// v1:info.task, v2: info.testId | v1:target.mode, v2:target.at.settings
Expand All @@ -834,7 +863,28 @@ class BehaviorInput {
specificUserInstruction: instructions.raw || instructions.instructions,
setupScriptDescription: target.setupScript ? target.setupScript.description : '',
setupTestPage: target.setupScript ? target.setupScript.name : undefined,
commands: commandsAndSettings,
commands: commandsAndSettings.map(cs => {
const foundCommandInfo = commands.find(
c => cs.commandId === c.id && cs.settings === c.settings
);
if (!foundCommandInfo || !foundCommandInfo.assertionExceptions) return cs;

// Only works for v2
let assertionExceptions = assertions.map(each => each.assertionId);
foundCommandInfo.assertionExceptions.forEach(each => {
let { priority, assertionId } = each;
const index = assertionExceptions.findIndex(each => each === assertionId);

priority = Number(priority);
assertionExceptions[index] = priority;
});
// Preserve default priority or update with exception
assertionExceptions = assertionExceptions.map((each, index) =>
isNaN(each) ? assertions[index].priority : each
);

return { ...cs, assertionExceptions };
}),
assertions: assertions.map(
({ priority, expectation, assertionStatement, tokenizedAssertionStatements }) => {
let assertion = tokenizedAssertionStatements
Expand Down Expand Up @@ -1187,6 +1237,7 @@ export class TestRunInputOutput {
description: command.settings,
text: command.settingsText,
instructions: command.settingsInstructions,
assertionExceptions: command.assertionExceptions,
},
atOutput: {
highlightRequired: false,
Expand Down
44 changes: 36 additions & 8 deletions client/resources/aria-at-test-run.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ export function instructionDocument(resultState, hooks) {
const resultUnexpectedBehavior = resultStateCommand.unexpected;

const {
commandSettings: { description: settings, text: settingsText },
commandSettings: { description: settings, text: settingsText, assertionExceptions },
} = resultStateCommand;

return {
Expand Down Expand Up @@ -248,7 +248,16 @@ export function instructionDocument(resultState, hooks) {
}?`,
},
assertions: [
...assertions.map(bind(assertionResult, commandIndex)),
...assertions
// Ignore assertion if level 0 priority exception found for assertion's command
.filter((each, index) => (assertionExceptions ? assertionExceptions[index] !== 0 : each))
.map(each =>
assertionResult(
commandIndex,
each,
assertions.findIndex(e => e === each)
)
),
...additionalAssertions.map(bind(additionalAssertionResult, commandIndex)),
],
unexpectedBehaviors: {
Expand Down Expand Up @@ -742,10 +751,20 @@ function resultsTableDocument(state) {
header: [
'Test result: ',
state.commands.some(
({ assertions, additionalAssertions, unexpected }) =>
[...assertions, ...additionalAssertions].some(
({ priority, result }) => priority === 1 && result !== CommonResultMap.PASS
) || unexpected.behaviors.some(({ checked }) => checked)
({
assertions,
additionalAssertions,
unexpected,
commandSettings: { assertionExceptions },
}) =>
[
// Ignore assertion if level 0 priority exception found for assertion's command
...assertions.filter((each, index) =>
assertionExceptions ? assertionExceptions[index] !== 0 : each
),
...additionalAssertions,
].some(({ priority, result }) => priority === 1 && result !== CommonResultMap.PASS) ||
unexpected.behaviors.some(({ checked }) => checked)
)
? 'FAIL'
: 'PASS',
Expand All @@ -758,11 +777,20 @@ function resultsTableDocument(state) {
details: 'Details',
},
commands: state.commands.map(command => {
const allAssertions = [...command.assertions, ...command.additionalAssertions];
const {
commandSettings: { assertionExceptions },
} = command;
const allAssertions = [
// Ignore assertion if level 0 priority exception found for assertion's command
...command.assertions.filter((each, index) =>
assertionExceptions ? assertionExceptions[index] !== 0 : each
),
...command.additionalAssertions,
];

let passingAssertions = ['No passing assertions.'];
let failingAssertions = ['No failing assertions.'];
let unexpectedBehaviors = ['No unexpect behaviors.'];
let unexpectedBehaviors = ['No unexpected behaviors.'];

if (allAssertions.some(({ result }) => result === CommonResultMap.PASS)) {
passingAssertions = allAssertions
Expand Down
1 change: 1 addition & 0 deletions client/resources/at-commands.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ export class commandsAPI {
return {
value,
key,
settings: mode,
};
})
);
Expand Down
8 changes: 8 additions & 0 deletions server/graphql-schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,14 @@ const graphqlSchema = gql`
A human-readable version of the command, such as "Control+Alt+Down"
"""
text: String!
"""
The AT mode this command may be getting ran in, such as quickNavOn,
browseMode, etc.
The same command can be ran during the same test, but in a different
mode.
"""
# TODO: Add link to list of known AT modes
atOperatingMode: String
}

"""
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
'use strict';

const { regenerateResultsAndRecalculateHashes } = require('./utils');
/** @type {import('sequelize-cli').Migration} */
module.exports = {
async up(queryInterface, Sequelize) {
const updateV2TestsToIncludeEmptyAssertionExceptions =
async transaction => {
const testPlanVersions = await queryInterface.sequelize.query(
`SELECT id, tests FROM "TestPlanVersion" WHERE metadata->>'testFormatVersion' = '2'`,
{
type: Sequelize.QueryTypes.SELECT,
transaction
}
);
await Promise.all(
testPlanVersions.map(async ({ id, tests }) => {
const updatedTests = JSON.stringify(
tests.map(test => {
test.assertions = test.assertions.map(
assertion => ({
...assertion,
assertionExceptions: []
})
);
return test;
})
);
await queryInterface.sequelize.query(
`UPDATE "TestPlanVersion" SET tests = ? WHERE id = ?`,
{ replacements: [updatedTests, id], transaction }
);
})
);
};

return queryInterface.sequelize.transaction(async transaction => {
await updateV2TestsToIncludeEmptyAssertionExceptions(transaction);

// Recalculate the hashes
await regenerateResultsAndRecalculateHashes(
queryInterface,
transaction
);
});
}
};
3 changes: 2 additions & 1 deletion server/models/services/TestsService.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ const getTests = parentRecord => {
id: commandKVs[0].key,
text: `${commandKVs[0].value}${
screenText ? ` (${screenText})` : ''
}`
}`,
atOperatingMode: scenario.settings
};
}
return { id: '', text: '' };
Expand Down
26 changes: 23 additions & 3 deletions server/resolvers/ScenarioResult/assertionResultsResolver.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,31 @@ const convertAssertionPriority = require('../helpers/convertAssertionPriority');
const assertionResultsResolver = (scenarioResult, { priority }) => {
if (!priority) return scenarioResult.assertionResults;

return scenarioResult.assertionResults.filter(
assertionResult =>
return scenarioResult.assertionResults.filter(assertionResult => {
if (assertionResult.assertion?.assertionExceptions?.length) {
const scenarioSettings = scenarioResult.scenario.settings;
const scenarioCommandId = scenarioResult.scenario.commandIds[0];

const foundException =
assertionResult.assertion.assertionExceptions.find(
exception =>
exception.settings === scenarioSettings &&
exception.commandId === scenarioCommandId
);

if (foundException) {
return (
convertAssertionPriority(foundException.priority) ===
convertAssertionPriority(priority)
);
}
}

return (
convertAssertionPriority(assertionResult.assertion.priority) ===
convertAssertionPriority(priority)
);
);
});
};

module.exports = assertionResultsResolver;
Loading
Loading