diff --git a/client/components/CandidateReview/CandidateTestPlanRun/index.jsx b/client/components/CandidateReview/CandidateTestPlanRun/index.jsx
index 33024afe0..ad98d49ad 100644
--- a/client/components/CandidateReview/CandidateTestPlanRun/index.jsx
+++ b/client/components/CandidateReview/CandidateTestPlanRun/index.jsx
@@ -29,9 +29,12 @@ import createIssueLink, {
import RunHistory from '../../common/RunHistory';
import { useUrlTestIndex } from '../../../hooks/useUrlTestIndex';
import { evaluateAuth } from '../../../utils/evaluateAuth';
+import summarizeAssertions from '../../../utils/summarizeAssertions.js';
import NotApprovedModal from '../CandidateModals/NotApprovedModal';
import FailingAssertionsSummaryTable from '../../FailingAssertionsSummary/Table';
import FailingAssertionsSummaryHeading from '../../FailingAssertionsSummary/Heading';
+import NegativeSideEffectsSummaryTable from '../../NegativeSideEffectsSummary/Table';
+import NegativeSideEffectsSummaryHeading from '../../NegativeSideEffectsSummary/Heading';
import styles from './CandidateTestPlanRun.module.css';
import feedbackStyles from '../FeedbackListItem/FeedbackListItem.module.css';
import testRunStyles from '../../TestRun/TestRun.module.css';
@@ -49,6 +52,8 @@ const vendorReviewStatusMap = {
IN_PROGRESS: 'In Progress',
APPROVED: 'Approved'
};
+const FAILING_ASSERTIONS_INDEX = -1;
+const NEGATIVE_SIDE_EFFECTS = -2;
const CandidateTestPlanRun = () => {
const { atId, testPlanVersionId } = useParams();
@@ -62,7 +67,7 @@ const CandidateTestPlanRun = () => {
const [viewedTests, setViewedTests] = useState([]);
const [testsLength, setTestsLength] = useState(0);
const [currentTestIndex, setCurrentTestIndex] = useUrlTestIndex({
- minTestIndex: -1,
+ minTestIndex: -2,
maxTestIndex: testsLength
});
const [showTestNavigator, setShowTestNavigator] = useState(true);
@@ -126,7 +131,9 @@ const CandidateTestPlanRun = () => {
skip: !testPlanReport
});
- const isSummaryView = currentTestIndex === -1;
+ const isSummaryView =
+ currentTestIndex === FAILING_ASSERTIONS_INDEX ||
+ currentTestIndex === NEGATIVE_SIDE_EFFECTS;
const isLaptopOrLarger = useMediaQuery({
query: '(min-width: 792px)'
@@ -139,7 +146,7 @@ const CandidateTestPlanRun = () => {
}, [data?.testPlanReports]);
const handleTestClick = async index => {
setCurrentTestIndex(index);
- if (index === -1) {
+ if (index < 0) {
// Summary view
setIsFirstTest(false);
setIsLastTest(false);
@@ -403,44 +410,56 @@ const CandidateTestPlanRun = () => {
const fileBugUrl = AtBugTrackerMap[at];
const getHeading = () => {
+ if (currentTestIndex === FAILING_ASSERTIONS_INDEX) {
+ return (
+ <>
+
+ Candidate Test Plan Review
+
+
+ >
+ );
+ }
+
+ if (currentTestIndex === NEGATIVE_SIDE_EFFECTS) {
+ return (
+ <>
+
+ Candidate Test Plan Review
+
+
+ >
+ );
+ }
+
return (
-
- {isSummaryView ? (
- <>
-
- Candidate Test Plan Review
-
-
- >
- ) : (
- <>
-
- Viewing Test {currentTest.title}, Test {currentTest.seq} of{' '}
- {tests.length}
- {currentTest.seq === tests.length
- ? 'You are on the last test.'
- : ''}
-
-
- Reviewing Test {currentTest.seq} of {tests.length}:
-
-
- {`${currentTest.seq}. ${currentTest.title}`}{' '}
- using {`${at}`}{' '}
- {`${testPlanReport?.latestAtVersionReleasedAt?.name ?? ''}`}
- {viewedTests.includes(currentTest.id) && !firstTimeViewing && ' '}
- {viewedTests.includes(currentTest.id) && !firstTimeViewing && (
-
- Previously Viewed
-
- )}
-
- >
- )}
-
+ <>
+
+ Viewing Test {currentTest.title}, Test {currentTest.seq} of{' '}
+ {tests.length}
+ {currentTest.seq === tests.length ? 'You are on the last test.' : ''}
+
+
+ Reviewing Test {currentTest.seq} of {tests.length}:
+
+
+ {`${currentTest.seq}. ${currentTest.title}`}{' '}
+ using {`${at}`}{' '}
+ {`${testPlanReport?.latestAtVersionReleasedAt?.name ?? ''}`}
+ {viewedTests.includes(currentTest.id) && !firstTimeViewing && ' '}
+ {viewedTests.includes(currentTest.id) && !firstTimeViewing && (
+
+ Previously Viewed
+
+ )}
+
+ >
);
};
@@ -560,93 +579,100 @@ const CandidateTestPlanRun = () => {
};
const getContent = () => {
+ if (currentTestIndex === FAILING_ASSERTIONS_INDEX) {
+ return (
+
+ `#${assertion.testIndex + 1}`}
+ />
+
+ );
+ }
+ if (currentTestIndex === NEGATIVE_SIDE_EFFECTS) {
+ return (
+
+ `#${assertion.testIndex + 1}`}
+ />
+
+ );
+ }
return (
-
- {isSummaryView ? (
-
- `#${assertion.testIndex + 1}`}
+ <>
+
+ {currentTest.title}
+
+
+ `Test Results for ${testPlanReport.browser.name}`
+ ),
+ 'Run History'
+ ]}
+ onClick={[
+ () => setShowInstructions(!showInstructions),
+ ...showBrowserClicks,
+ () => setShowRunHistory(!showRunHistory)
+ ]}
+ expanded={[showInstructions, ...showBrowserBools, showRunHistory]}
+ disclosureContainerView={[
+ ,
+ ...testPlanReports.map(testPlanReport => {
+ const testResult =
+ testPlanReport.finalizedTestResults[currentTestIndex];
+
+ const assertionsSummary = summarizeAssertions(
+ getMetrics({
+ testResult
+ })
+ );
+
+ return (
+ <>
+
+ Test Results ({assertionsSummary})
+
+
+ >
+ );
+ }),
+
-
- ) : (
- <>
-
- {currentTest.title}
-
-
- `Test Results for ${testPlanReport.browser.name}`
- ),
- 'Run History'
- ]}
- onClick={[
- () => setShowInstructions(!showInstructions),
- ...showBrowserClicks,
- () => setShowRunHistory(!showRunHistory)
- ]}
- expanded={[showInstructions, ...showBrowserBools, showRunHistory]}
- disclosureContainerView={[
- ,
- ...testPlanReports.map(testPlanReport => {
- const testResult =
- testPlanReport.finalizedTestResults[currentTestIndex];
-
- const {
- assertionsPassedCount,
- mustAssertionsFailedCount,
- shouldAssertionsFailedCount,
- mayAssertionsFailedCount
- } = getMetrics({ testResult });
-
- const mustShouldAssertionsFailedCount =
- mustAssertionsFailedCount + shouldAssertionsFailedCount;
-
- return (
- <>
-
- Test Results (
- {assertionsPassedCount} passed,
- {mustShouldAssertionsFailedCount} failed,
- {mayAssertionsFailedCount} unsupported)
-
-
- >
- );
- }),
-
- ]}
- >
- >
- )}
-
+ ]}
+ >
+ >
);
};
@@ -668,14 +694,14 @@ const CandidateTestPlanRun = () => {
/>
- {getHeading()}
+ {getHeading()}
{getTestInfo()}
{getFeedback()}
- {getContent()}
+ {getContent()}
{
+ return (
+
+ Summary of Negative Side Effects (
+ {metrics.severeImpactFailedAssertionCount} severe,{' '}
+ {metrics.moderateImpactFailedAssertionCount} moderate)
+
+ );
+};
+
+NegativeSideEffectsSummaryHeading.propTypes = {
+ metrics: TestPlanReportMetricsPropType.isRequired,
+ as: PropTypes.elementType
+};
+
+export default NegativeSideEffectsSummaryHeading;
diff --git a/client/components/NegativeSideEffectsSummary/Table.jsx b/client/components/NegativeSideEffectsSummary/Table.jsx
new file mode 100644
index 000000000..87bee0129
--- /dev/null
+++ b/client/components/NegativeSideEffectsSummary/Table.jsx
@@ -0,0 +1,60 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Table } from 'react-bootstrap';
+import { Link } from 'react-router-dom';
+import { useNegativeSideEffects } from '../../hooks/useNegativeSideEffects';
+import { TestPlanReportPropType } from '../common/proptypes';
+
+const NegativeSideEffectsSummaryTable = ({
+ testPlanReport,
+ getLinkUrl = assertion => `#result-${assertion.testId}`,
+ LinkComponent = Link
+}) => {
+ const negativeSideEffects = useNegativeSideEffects(testPlanReport);
+ const { metrics } = testPlanReport;
+
+ if (metrics.unexpectedBehaviorCount.length === 0) return null;
+
+ return (
+ <>
+
+
+
+ Test Name
+ Command
+ Side Effect
+ Details
+ Impact
+
+
+
+ {negativeSideEffects.map((assertion, index) => (
+
+
+
+ {assertion.testTitle}
+
+
+ {assertion.scenarioCommands}
+ {assertion.text}
+ {assertion.details}
+ {assertion.impact}
+
+ ))}
+
+
+ >
+ );
+};
+
+NegativeSideEffectsSummaryTable.propTypes = {
+ testPlanReport: TestPlanReportPropType.isRequired,
+ getLinkUrl: PropTypes.func,
+ LinkComponent: PropTypes.elementType
+};
+
+export default NegativeSideEffectsSummaryTable;
diff --git a/client/components/Reports/SummarizeTestPlanReport.jsx b/client/components/Reports/SummarizeTestPlanReport.jsx
index 604d63616..c40fa8f84 100644
--- a/client/components/Reports/SummarizeTestPlanReport.jsx
+++ b/client/components/Reports/SummarizeTestPlanReport.jsx
@@ -17,6 +17,7 @@ import TestPlanResultsTable from '../common/TestPlanResultsTable';
import DisclosureComponent from '../common/DisclosureComponent';
import { Link, Navigate, useLocation, useParams } from 'react-router-dom';
import createIssueLink from '../../utils/createIssueLink';
+import summarizeAssertions from '../../utils/summarizeAssertions.js';
import RunHistory from '../common/RunHistory';
import {
TestPlanReportPropType,
@@ -24,6 +25,8 @@ import {
} from '../common/proptypes';
import FailingAssertionsSummaryTable from '../FailingAssertionsSummary/Table';
import FailingAssertionsSummaryHeading from '../FailingAssertionsSummary/Heading';
+import NegativeSideEffectsSummaryTable from '../NegativeSideEffectsSummary/Table';
+import NegativeSideEffectsSummaryHeading from '../NegativeSideEffectsSummary/Heading';
import styles from './SummarizeTestPlanReport.module.css';
import commonStyles from '../common/styles.module.css';
@@ -187,6 +190,25 @@ const SummarizeTestPlanReport = ({ testPlanVersion, testPlanReports }) => {
);
};
+ const renderNegativeSideEffectsSummary = () => {
+ if (testPlanReport.metrics.unexpectedBehaviorCount === 0) {
+ return null;
+ }
+
+ return (
+ <>
+
+
+ `/report/${testPlanVersion.id}/targets/${testPlanReport.id}#result-${assertion.testResultId}`
+ }
+ />
+ >
+ );
+ };
+
return (
@@ -262,6 +284,7 @@ const SummarizeTestPlanReport = ({ testPlanVersion, testPlanReports }) => {
{renderVersionsSummaryTable()}
{renderResultsForTargetTable()}
{renderFailingAssertionsSummary()}
+ {renderNegativeSideEffectsSummary()}
{testPlanReport.finalizedTestResults.map((testResult, index) => {
const test = testResult.test;
@@ -288,26 +311,17 @@ const SummarizeTestPlanReport = ({ testPlanVersion, testPlanReports }) => {
'https://aria-at.netlify.app'
);
- const {
- assertionsPassedCount,
- mustAssertionsFailedCount,
- shouldAssertionsFailedCount,
- mayAssertionsFailedCount
- } = getMetrics({
- testResult
- });
-
- const mustShouldAssertionsFailedCount =
- mustAssertionsFailedCount + shouldAssertionsFailedCount;
+ const assertionsSummary = summarizeAssertions(
+ getMetrics({
+ testResult
+ })
+ );
return (
- Test {index + 1}: {test.title} (
- {assertionsPassedCount} passed,
- {mustShouldAssertionsFailedCount} failed,
- {mayAssertionsFailedCount} unsupported)
+ Test {index + 1}: {test.title} ({assertionsSummary})
diff --git a/client/components/TestRenderer/AssertionsFieldset/index.jsx b/client/components/TestRenderer/AssertionsFieldset/index.jsx
index 1c61bc11b..7cfab0837 100644
--- a/client/components/TestRenderer/AssertionsFieldset/index.jsx
+++ b/client/components/TestRenderer/AssertionsFieldset/index.jsx
@@ -7,7 +7,8 @@ const AssertionsFieldset = ({
assertions,
commandIndex,
assertionsHeader,
- readOnly = false
+ readOnly = false,
+ disabled
}) => {
// Handle case where build process didn't include assertionResponseQuestion
const normalizedHeader = useMemo(() => {
@@ -28,6 +29,7 @@ const AssertionsFieldset = ({
{description[0]}
@@ -63,7 +65,8 @@ AssertionsFieldset.propTypes = {
assertions: PropTypes.array.isRequired,
commandIndex: PropTypes.number.isRequired,
assertionsHeader: PropTypes.object.isRequired,
- readOnly: PropTypes.bool
+ readOnly: PropTypes.bool,
+ disabled: PropTypes.bool.isRequired
};
export default AssertionsFieldset;
diff --git a/client/components/TestRenderer/CommandResults/index.jsx b/client/components/TestRenderer/CommandResults/index.jsx
index 1c15711aa..d149c088d 100644
--- a/client/components/TestRenderer/CommandResults/index.jsx
+++ b/client/components/TestRenderer/CommandResults/index.jsx
@@ -1,14 +1,17 @@
import React from 'react';
import createIssueLink from '@client/utils/createIssueLink';
-import { AtOutputPropType } from '../../common/proptypes';
+import { AtOutputPropType, UntestablePropType } from '../../common/proptypes';
import PropTypes from 'prop-types';
+import clsx from 'clsx';
import AssertionsFieldset from '../AssertionsFieldset';
import OutputTextArea from '../OutputTextArea';
import UnexpectedBehaviorsFieldset from '../UnexpectedBehaviorsFieldset';
+import styles from '../TestRenderer.module.css';
const CommandResults = ({
header,
atOutput,
+ untestable,
assertions,
unexpectedBehaviors,
assertionsHeader,
@@ -33,17 +36,43 @@ const CommandResults = ({
isSubmitted={isSubmitted}
readOnly={isReviewingBot || isReadOnly}
/>
+
+
+ untestable.change(e.target.checked)}
+ autoFocus={isSubmitted && untestable.focus}
+ checked={untestable.value}
+ />
+ {untestable.description[0]}
+ {isSubmitted && (
+
+ {untestable.description[1].description}
+
+ )}
+
+
Raise an issue for {commandString}
@@ -55,6 +84,7 @@ const CommandResults = ({
CommandResults.propTypes = {
header: PropTypes.string.isRequired,
atOutput: AtOutputPropType.isRequired,
+ untestable: UntestablePropType.isRequired,
assertions: PropTypes.array.isRequired,
unexpectedBehaviors: PropTypes.object.isRequired,
assertionsHeader: PropTypes.object.isRequired,
diff --git a/client/components/TestRenderer/TestRenderer.module.css b/client/components/TestRenderer/TestRenderer.module.css
index 14d5d8091..f05309fbb 100644
--- a/client/components/TestRenderer/TestRenderer.module.css
+++ b/client/components/TestRenderer/TestRenderer.module.css
@@ -165,6 +165,14 @@ label {
}
}
+ &.untestable-label {
+ margin-bottom: var(--8px);
+
+ input {
+ margin-right: var(--4px);
+ }
+ }
+
&.assertions-label {
margin-bottom: var(--8px);
margin-right: var(--8px);
diff --git a/client/components/TestRenderer/UnexpectedBehaviorsFieldset/index.jsx b/client/components/TestRenderer/UnexpectedBehaviorsFieldset/index.jsx
index 156b2f734..518d56e3d 100644
--- a/client/components/TestRenderer/UnexpectedBehaviorsFieldset/index.jsx
+++ b/client/components/TestRenderer/UnexpectedBehaviorsFieldset/index.jsx
@@ -7,10 +7,10 @@ const UnexpectedBehaviorsFieldset = ({
commandIndex,
unexpectedBehaviors,
isSubmitted,
- readOnly = false
+ readOnly = false,
+ forceYes
}) => {
const impactOptions = ['Moderate', 'Severe'];
-
const handleUnexpectedBehaviorsExistRadioClick = e => {
if (readOnly) e.preventDefault();
else {
@@ -45,8 +45,9 @@ const UnexpectedBehaviorsFieldset = ({
id={`problem-${commandIndex}-true`}
name={`problem-${commandIndex}`}
autoFocus={isSubmitted && unexpectedBehaviors.passChoice.focus}
- defaultChecked={unexpectedBehaviors.passChoice.checked}
- onClick={handleUnexpectedBehaviorsExistRadioClick}
+ checked={unexpectedBehaviors.passChoice.checked}
+ onChange={handleUnexpectedBehaviorsExistRadioClick}
+ disabled={forceYes}
/>
- {description} behavior occurred
+ {description}
{/* Impact select */}
@@ -201,7 +202,8 @@ UnexpectedBehaviorsFieldset.propTypes = {
commandIndex: PropTypes.number.isRequired,
unexpectedBehaviors: PropTypes.object.isRequired,
isSubmitted: PropTypes.bool,
- readOnly: PropTypes.bool
+ readOnly: PropTypes.bool,
+ forceYes: PropTypes.bool.isRequired
};
export default UnexpectedBehaviorsFieldset;
diff --git a/client/components/TestRenderer/index.jsx b/client/components/TestRenderer/index.jsx
index 9e954595b..2f8f3b0e9 100644
--- a/client/components/TestRenderer/index.jsx
+++ b/client/components/TestRenderer/index.jsx
@@ -15,6 +15,7 @@ import {
} from '../../resources/aria-at-test-io-format.mjs';
import { TestWindow } from '../../resources/aria-at-test-window.mjs';
import { evaluateAtNameKey } from '../../utils/aria';
+import summarizeAssertions from '../../utils/summarizeAssertions.js';
import CommandResults from './CommandResults';
import supportJson from '../../resources/support.json';
import commandsJson from '../../resources/commands.json';
@@ -131,6 +132,8 @@ const TestRenderer = ({
for (let i = 0; i < scenarioResults.length; i++) {
let {
output,
+ untestable,
+ untestableHighlightRequired,
assertionResults,
hasUnexpected,
unexpectedBehaviors,
@@ -141,6 +144,9 @@ const TestRenderer = ({
if (output) commands[i].atOutput.value = output;
commands[i].atOutput.highlightRequired = highlightRequired;
+ if (untestable) commands[i].untestable.value = untestable;
+ commands[i].untestable.highlightRequired = !!untestableHighlightRequired;
+
// Required because assertionResults can now be returned without an id if there is a 0-priority exception
// applied
assertionResults = assertionResults.filter(el => !!el.id);
@@ -266,6 +272,9 @@ const TestRenderer = ({
const atOutputError = item.atOutput.highlightRequired;
if (atOutputError) return true;
+ const untestableError = item.untestable.highlightRequired;
+ if (untestableError) return true;
+
const unexpectedError = item.unexpected.highlightRequired;
if (unexpectedError) return true;
@@ -289,6 +298,10 @@ const TestRenderer = ({
const atOutputError = item.atOutput.description[1].highlightRequired;
if (atOutputError) return true;
+ const untestableError =
+ item.untestable.description[1].highlightRequired;
+ if (untestableError) return true;
+
const unexpectedBehaviorError =
item.unexpectedBehaviors.description[1].highlightRequired;
if (unexpectedBehaviorError) return true;
@@ -374,27 +387,16 @@ const TestRenderer = ({
const { results } = submitResult;
const { header } = results;
- const {
- assertionsPassedCount,
- mustAssertionsFailedCount,
- shouldAssertionsFailedCount,
- mayAssertionsFailedCount
- } = getMetrics({
- testResult
- });
-
- const mustShouldAssertionsFailedCount =
- mustAssertionsFailedCount + shouldAssertionsFailedCount;
+ const assertionsSummary = summarizeAssertions(
+ getMetrics({
+ testResult
+ })
+ );
return (
<>
{header}
-
- Test Results (
- {assertionsPassedCount} passed,
- {mustShouldAssertionsFailedCount} failed,
- {mayAssertionsFailedCount} unsupported)
-
+ Test Results ({assertionsSummary})
{
+ return isVendor && testPlanReport.metrics.unexpectedBehaviorCount > 0;
+ }, [isVendor, testPlanReport]);
+
return (
@@ -74,7 +78,7 @@ const TestNavigator = ({
e.preventDefault();
await handleTestClick(-1);
}}
- href="#summary"
+ href="#summary-assertions"
className={styles.testName}
aria-current={currentTestIndex === -1}
>
@@ -86,6 +90,25 @@ const TestNavigator = ({
/>
)}
+ {shouldShowNegativeSideEffectsSummary && (
+
+ )}
{tests.map((test, index) => {
let resultClassName = isReadOnly
? styles.missing
diff --git a/client/components/TestRun/index.jsx b/client/components/TestRun/index.jsx
index e2f80dae3..fea290d6d 100644
--- a/client/components/TestRun/index.jsx
+++ b/client/components/TestRun/index.jsx
@@ -418,7 +418,7 @@ const TestRun = () => {
let unexpectedBehaviors = null;
// collect variables
- const { atOutput, assertions, unexpected } = commands[i];
+ const { atOutput, untestable, assertions, unexpected } = commands[i];
// process assertion results
for (let j = 0; j < assertions.length; j++) {
@@ -469,6 +469,12 @@ const TestRun = () => {
scenarioResult.output = atOutput.value ? atOutput.value : null;
if (captureHighlightRequired)
scenarioResult.highlightRequired = atOutput.highlightRequired;
+
+ scenarioResult.untestable = untestable.value ? untestable.value : null;
+ if (captureHighlightRequired && untestable)
+ scenarioResult.untestableHighlightRequired =
+ untestable.highlightRequired;
+
scenarioResult.assertionResults = [...assertionResults];
scenarioResult.hasUnexpected = hasUnexpected;
scenarioResult.unexpectedBehaviors = unexpectedBehaviors
@@ -670,11 +676,13 @@ const TestRun = () => {
assertionResults,
id,
output,
+ untestable,
hasUnexpected,
unexpectedBehaviors
}) => ({
id,
output: output,
+ untestable: untestable,
hasUnexpected,
unexpectedBehaviors: unexpectedBehaviors?.map(
({ id, impact, details }) => ({
diff --git a/client/components/common/TestPlanResultsTable/index.jsx b/client/components/common/TestPlanResultsTable/index.jsx
index 3dba6e41d..7141671e2 100644
--- a/client/components/common/TestPlanResultsTable/index.jsx
+++ b/client/components/common/TestPlanResultsTable/index.jsx
@@ -4,19 +4,33 @@ import { Table } from 'react-bootstrap';
import nextId from 'react-id-generator';
import { getMetrics } from 'shared';
import './TestPlanResultsTable.css';
-import { TestPropType, TestResultPropType } from '../proptypes';
+import {
+ TestPlanReportMetricsPropType,
+ TestPropType,
+ TestResultPropType
+} from '../proptypes';
-const getAssertionResultText = (passed, priority) => {
- if (priority === 'MAY') {
+const getAssertionResultText = (assertionResult, untestable) => {
+ const { passed, priorityString, describesSideEffects } = assertionResult;
+
+ // In untestable scenarios, the result of test-specific assertions should be
+ // reported as "untestable" because their state is indeterminate. The other
+ // assertions (that is, those describing the presence of side effects) should
+ // be reported as "Passed" or "Failed" as normal because the absence/presence
+ // of side effects *can* be conclusively reported.
+ if (untestable && !describesSideEffects) {
+ return 'Untestable';
+ }
+ if (priorityString === 'MAY') {
return passed ? 'Supported' : 'Unsupported';
}
return passed ? 'Passed' : 'Failed';
};
-const renderAssertionRow = (assertionResult, priorityString) => {
+const renderAssertionRow = (assertionResult, untestable) => {
return (
- {priorityString}
+ {assertionResult.priorityString}
{assertionResult.assertion.phrase
? assertionResult.assertion.phrase.charAt(0).toUpperCase() +
@@ -24,11 +38,43 @@ const renderAssertionRow = (assertionResult, priorityString) => {
: assertionResult.assertion.text.charAt(0).toUpperCase() +
assertionResult.assertion.text.slice(1)}
- {getAssertionResultText(assertionResult.passed, priorityString)}
+ {getAssertionResultText(assertionResult, untestable)}
);
};
+const CommandHeading = ({ level, commandsString, metrics }) => {
+ const Heading = `h${level}`;
+ const {
+ assertionsPassedCount,
+ assertionsUntestableCount,
+ mustAssertionsFailedCount,
+ shouldAssertionsFailedCount,
+ mayAssertionsFailedCount
+ } = metrics;
+
+ const mustShouldAssertionsFailedCount =
+ mustAssertionsFailedCount + shouldAssertionsFailedCount;
+ const ambiguousText = assertionsUntestableCount
+ ? `${assertionsUntestableCount} untestable`
+ : `${mayAssertionsFailedCount} unsupported`;
+
+ return (
+
+ {commandsString} Results:
+ {assertionsPassedCount} passed,
+ {mustShouldAssertionsFailedCount} failed,
+ {ambiguousText}
+
+ );
+};
+
+CommandHeading.propTypes = {
+ level: PropTypes.number.isRequired,
+ commandsString: PropTypes.string.isRequired,
+ metrics: TestPlanReportMetricsPropType.isRequired
+};
+
const TestPlanResultsTable = ({
test,
testResult,
@@ -36,23 +82,17 @@ const TestPlanResultsTable = ({
optionalHeader = null,
commandHeadingLevel = 3
}) => {
- const CommandHeading = `h${commandHeadingLevel}`;
+ const NegativeSideEffectsHeading = `h${commandHeadingLevel}`;
return (
<>
{optionalHeader}
{testResult.scenarioResults.map((scenarioResult, index) => {
+ const metrics = getMetrics({ scenarioResult });
const {
- assertionsPassedCount,
- mustAssertionsFailedCount,
- shouldAssertionsFailedCount,
- mayAssertionsFailedCount,
severeImpactPassedAssertionCount,
moderateImpactPassedAssertionCount
- } = getMetrics({ scenarioResult });
-
- const mustShouldAssertionsFailedCount =
- mustAssertionsFailedCount + shouldAssertionsFailedCount;
+ } = metrics;
const hasNoSevereUnexpectedBehavior =
severeImpactPassedAssertionCount > 0;
@@ -100,8 +140,9 @@ const TestPlanResultsTable = ({
{
id: `UnexpectedBehavior_MUST_${nextId()}`,
assertion: {
- text: 'Other behaviors that create severe negative impacts are not exhibited'
+ text: 'Severe negative side effects do not occur'
},
+ describesSideEffects: true,
passed: hasNoSevereUnexpectedBehavior,
priorityString: 'MUST'
},
@@ -113,8 +154,9 @@ const TestPlanResultsTable = ({
{
id: `UnexpectedBehavior_SHOULD_${nextId()}`,
assertion: {
- text: 'Other behaviors that create moderate negative impacts are not exhibited'
+ text: 'Moderate negative side effects do not occur'
},
+ describesSideEffects: true,
passed: hasNoModerateUnexpectedBehavior,
priorityString: 'SHOULD'
},
@@ -126,12 +168,11 @@ const TestPlanResultsTable = ({
return (
-
- {commandsString} Results:
- {assertionsPassedCount} passed,
- {mustShouldAssertionsFailedCount} failed,
- {mayAssertionsFailedCount} unsupported
-
+
{test.at?.name} Response:
@@ -154,42 +195,43 @@ const TestPlanResultsTable = ({
{sortedAssertionResults.map(assertionResult =>
- renderAssertionRow(
- assertionResult,
- assertionResult.priorityString
- )
+ renderAssertionRow(assertionResult, scenarioResult.untestable)
)}
- Other behaviors that create negative impact:{' '}
{scenarioResult.unexpectedBehaviors.length ? (
-
-
-
- Behavior
- Details
- Impact
-
-
-
- {scenarioResult.unexpectedBehaviors.map(
- ({ id, text, details, impact }) => (
-
- {text}
- {details}
- {impact}
-
- )
- )}
-
-
+ <>
+
+ Negative side effects of {commandsString}
+
+
+
+
+ Side Effect
+ Details
+ Impact
+
+
+
+ {scenarioResult.unexpectedBehaviors.map(
+ ({ id, text, details, impact }) => (
+
+ {text}
+ {details}
+ {impact}
+
+ )
+ )}
+
+
+ >
) : (
- 'None'
+ `The command '${commandsString}' did not cause any negative side effects.`
)}
{/* Do not show separator below last item */}
{index !== testResult.scenarioResults.length - 1 ? (
diff --git a/client/components/common/fragments/ScenarioResult.js b/client/components/common/fragments/ScenarioResult.js
index d385d8d0a..c17688304 100644
--- a/client/components/common/fragments/ScenarioResult.js
+++ b/client/components/common/fragments/ScenarioResult.js
@@ -28,6 +28,7 @@ const SCENARIO_RESULT_FIELDS = (type = 'simple') => {
__typename
id
output
+ untestable
scenario {
id
commands {
diff --git a/client/components/common/proptypes/index.js b/client/components/common/proptypes/index.js
index 5fd92c7d0..3ad3581cb 100644
--- a/client/components/common/proptypes/index.js
+++ b/client/components/common/proptypes/index.js
@@ -354,3 +354,18 @@ export const AtOutputPropType = PropTypes.shape({
change: PropTypes.func.isRequired,
focus: PropTypes.bool.isRequired
});
+
+export const UntestablePropType = PropTypes.shape({
+ description: PropTypes.arrayOf(
+ PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.shape({
+ highlightRequired: PropTypes.bool.isRequired,
+ description: PropTypes.string.isRequired
+ })
+ ])
+ ).isRequired,
+ change: PropTypes.func.isRequired,
+ value: PropTypes.bool.isRequired,
+ focus: PropTypes.bool.isRequired
+});
diff --git a/client/hooks/useFailingAssertions.js b/client/hooks/useFailingAssertions.js
index 98768d840..bb2b389cc 100644
--- a/client/hooks/useFailingAssertions.js
+++ b/client/hooks/useFailingAssertions.js
@@ -58,9 +58,9 @@ export const useFailingAssertions = testPlanReport => {
...commonResult,
assertionText:
unexpectedBehavior.impact.toLowerCase() === 'moderate'
- ? 'Other behaviors that create moderate negative impacts are not exhibited'
+ ? 'Moderate negative side effects do not occur'
: unexpectedBehavior.impact.toLowerCase() === 'severe'
- ? 'Other behaviors that create severe negative impacts are not exhibited'
+ ? 'Severe negative side effects do not occur'
: 'N/A',
priority:
unexpectedBehavior.impact.toLowerCase() === 'moderate'
diff --git a/client/hooks/useNegativeSideEffects.js b/client/hooks/useNegativeSideEffects.js
new file mode 100644
index 000000000..ccfa77b3d
--- /dev/null
+++ b/client/hooks/useNegativeSideEffects.js
@@ -0,0 +1,46 @@
+import { useMemo } from 'react';
+
+export const useNegativeSideEffects = testPlanReport => {
+ return useMemo(() => {
+ if (!testPlanReport?.finalizedTestResults) {
+ return [];
+ }
+
+ const negativeSideEffects = testPlanReport.finalizedTestResults.flatMap(
+ (testResult, testIndex) => {
+ return testResult.scenarioResults.flatMap(scenarioResult => {
+ const commonResult = {
+ testResultId: testResult.id,
+ testIndex,
+ testTitle: testResult.test.title,
+ scenarioCommands: scenarioResult.scenario.commands
+ .map((cmd, index) => {
+ if (index === scenarioResult.scenario.commands.length - 1) {
+ return cmd.text;
+ }
+ // Prevent instances of duplicated setting in brackets.
+ // eg. Down Arrow (virtual cursor active) then Down Arrow (virtual cursor active)
+ //
+ // Expectation is Down Arrow then Down Arrow (virtual cursor active), because the setting will always be
+ // the same for the listed key combination.
+ //
+ // Some revision of how that key combination + setting is rendered may be useful
+ return cmd.text.split(' (')[0];
+ })
+ .join(' then '),
+ commandId: `${
+ scenarioResult.scenario.id
+ }_${scenarioResult.scenario.commands.map(cmd => cmd.id).join('_')}`
+ };
+
+ return scenarioResult.unexpectedBehaviors.map(unexpectedBehavior => ({
+ ...commonResult,
+ ...unexpectedBehavior
+ }));
+ });
+ }
+ );
+
+ return negativeSideEffects;
+ }, [testPlanReport]);
+};
diff --git a/client/hooks/useUrlTestIndex.js b/client/hooks/useUrlTestIndex.js
index 4d312ad19..997fe459c 100644
--- a/client/hooks/useUrlTestIndex.js
+++ b/client/hooks/useUrlTestIndex.js
@@ -1,14 +1,28 @@
import { useLocation, useNavigate } from 'react-router-dom';
import { useEffect, useState } from 'react';
+/**
+ * In the context of this hook, two negative values for test indices carry
+ * special meaning:
+ *
+ * * -1: display the "Summary of Failing Assertions"
+ * * -2: display the "Summary of Negative Side Effects"
+ */
+const FAILING_ASSERTIONS_INDEX = -1;
+const NEGATIVE_SIDE_EFFECTS = -2;
+
export const useUrlTestIndex = ({ minTestIndex = 0, maxTestIndex }) => {
const location = useLocation();
const navigate = useNavigate();
const [currentTestIndex, setCurrentTestIndex] = useState(minTestIndex);
const getTestIndex = () => {
- if (location.hash === '#summary') {
- return -1;
+ if (location.hash === '#summary-assertions') {
+ return FAILING_ASSERTIONS_INDEX;
+ }
+
+ if (location.hash === '#summary-side-effects') {
+ return NEGATIVE_SIDE_EFFECTS;
}
// Remove the '#' character
@@ -29,8 +43,12 @@ export const useUrlTestIndex = ({ minTestIndex = 0, maxTestIndex }) => {
const updateTestIndex = index => {
// Special case for summary
- if (index === -1) {
- navigate(`${location.pathname}#summary`, { replace: true });
+ if (index === FAILING_ASSERTIONS_INDEX) {
+ navigate(`${location.pathname}#summary-assertions`, { replace: true });
+ return;
+ }
+ if (index === NEGATIVE_SIDE_EFFECTS) {
+ navigate(`${location.pathname}#summary-side-effects`, { replace: true });
return;
}
diff --git a/client/tests/e2e/CandidateReview.e2e.test.js b/client/tests/e2e/CandidateReview.e2e.test.js
index 06489aef6..3e8e8e00b 100644
--- a/client/tests/e2e/CandidateReview.e2e.test.js
+++ b/client/tests/e2e/CandidateReview.e2e.test.js
@@ -132,7 +132,7 @@ describe('Candidate Review when signed in as vendor', () => {
'::-p-text(Review status by JAWS Representative: In Progress)'
);
- await page.click('a[href="#summary"]');
+ await page.click('a[href="#summary-assertions"]');
// Check navigation buttons in summary view
const previousButton = await page.$('button::-p-text(Previous Test)');
@@ -147,7 +147,7 @@ describe('Candidate Review when signed in as vendor', () => {
await page.waitForSelector('h1::-p-text(1.)');
// Check summary link aria-current attribute
const summaryLinkAriaCurrent = await page.$eval(
- 'a[href="#summary"]',
+ 'a[href="#summary-assertions"]',
el => el.getAttribute('aria-current')
);
expect(summaryLinkAriaCurrent).toBe('false');
@@ -159,7 +159,9 @@ describe('Candidate Review when signed in as vendor', () => {
await previousFromFirstTest.click();
// Should be back on summary
- await page.waitForSelector('a[href="#summary"][aria-current="true"]');
+ await page.waitForSelector(
+ 'a[href="#summary-assertions"][aria-current="true"]'
+ );
expect(consoleErrors).toHaveLength(0);
}
@@ -184,7 +186,7 @@ describe('Candidate Review when signed in as vendor', () => {
);
// Check for summary specific content
- await page.click('a[href="#summary"]');
+ await page.click('a[href="#summary-assertions"]');
await page.waitForSelector(
'h1::-p-text(Summary of Failing Assertions)'
);
diff --git a/client/tests/e2e/TestRun.e2e.test.js b/client/tests/e2e/TestRun.e2e.test.js
index 4a39b13e4..f100d175c 100644
--- a/client/tests/e2e/TestRun.e2e.test.js
+++ b/client/tests/e2e/TestRun.e2e.test.js
@@ -110,6 +110,8 @@ describe('Test Run when not signed in', () => {
});
describe('Test Run when signed in as tester', () => {
+ const submitResultsButtonSelector =
+ 'button[class="btn btn-primary"] ::-p-text(Submit Results)';
const assignSelfAndNavigateToRun = async (
page,
{
@@ -171,8 +173,6 @@ describe('Test Run when signed in as tester', () => {
// Confirm that submission cannot happen with empty form
// Specificity with selector because there's a 2nd 'hidden' button coming
// from the harness which is what is actually called for the submit event
- const submitResultsButtonSelector =
- 'button[class="btn btn-primary"] ::-p-text(Submit Results)';
await page.waitForSelector(submitResultsButtonSelector);
await page.click(submitResultsButtonSelector);
await page.waitForNetworkIdle();
@@ -313,27 +313,27 @@ describe('Test Run when signed in as tester', () => {
});
});
- it('inputs results and navigates between tests to confirm saving', async () => {
- async function getGeneratedCheckedAssertionCount(page) {
- return await page.evaluate(() => {
- const radioGroups = document.querySelectorAll(
- 'input[type="radio"][id^="pass-"]'
- );
- let yesCount = 0;
-
- for (let i = 0; i < radioGroups.length; i += 2) {
- if (i % 4 === 0) {
- radioGroups[i].click(); // Click 'Yes' radio
- yesCount++;
- } else {
- radioGroups[i + 1].click(); // Click 'No' radio
- }
+ async function getGeneratedCheckedAssertionCount(page) {
+ return await page.evaluate(() => {
+ const radioGroups = document.querySelectorAll(
+ 'input[type="radio"][id^="pass-"]'
+ );
+ let yesCount = 0;
+
+ for (let i = 0; i < radioGroups.length; i += 2) {
+ if (i % 4 === 0) {
+ radioGroups[i].click(); // Click 'Yes' radio
+ yesCount++;
+ } else {
+ radioGroups[i + 1].click(); // Click 'No' radio
}
+ }
- return yesCount;
- });
- }
+ return yesCount;
+ });
+ }
+ it('inputs results and navigates between tests to confirm saving', async () => {
await getPage({ role: 'tester', url: '/test-queue' }, async page => {
await assignSelfAndNavigateToRun(page);
@@ -454,6 +454,114 @@ describe('Test Run when signed in as tester', () => {
});
});
+ it('marks assertions as untestable', async () => {
+ const countChecked = page => {
+ return page.evaluate(() => {
+ return {
+ passing: document.querySelectorAll(
+ '[type="radio"][id$=-yes]:not(:disabled):checked'
+ ).length,
+ failing: document.querySelectorAll(
+ '[type="radio"][id$=-no]:not(:disabled):checked'
+ ).length
+ };
+ });
+ };
+ await getPage({ role: 'tester', url: '/test-queue' }, async page => {
+ await assignSelfAndNavigateToRun(page, {
+ testPlanSectionButtonSelector: 'button#disclosure-btn-alert-0',
+ testPlanTableSelector:
+ 'table[aria-label="Reports for Alert Example V22.04.14 in draft phase"]'
+ });
+
+ await page.waitForSelector('h1 ::-p-text(Test 1:)');
+ await page.waitForSelector('button ::-p-text(Submit Results)');
+
+ expect(await countChecked(page)).toEqual({
+ passing: 0,
+ failing: 0
+ });
+
+ await getGeneratedCheckedAssertionCount(page);
+
+ expect(await countChecked(page)).toEqual({
+ passing: 3,
+ failing: 3
+ });
+
+ await page.click('label ::-p-text(untestable)');
+
+ expect(await countChecked(page)).toEqual({
+ passing: 2,
+ failing: 2
+ });
+
+ // The initial submission should fail because no SEVERE unexpected
+ // behaviors have been specified.
+ await page.click(submitResultsButtonSelector);
+ await page.waitForNetworkIdle();
+ await page.waitForSelector('h1 ::-p-text(Test 1:)');
+ await page.waitForSelector('button ::-p-text(Submit Results)');
+
+ await page.click('label ::-p-text(excessively verbose)');
+ await page.select('.unexpected-behaviors-label select', 'SEVERE');
+ await page.type(
+ '.unexpected-behaviors-label input[type=text]',
+ 'anything'
+ );
+
+ await handlePageSubmit(page, { expectConflicts: false });
+
+ const text = await page.evaluate(() => {
+ // Scrape text and normalize empty space characters.
+ const text = el => el.innerText.replace(/\s/g, ' ');
+ const readTable = el => {
+ return Array.from(el.querySelectorAll('tr')).map(tr =>
+ Array.from(tr.querySelectorAll('td')).map(td => text(td))
+ );
+ };
+ return [text(document.querySelector('main h2'))].concat(
+ Array.from(document.querySelectorAll('main h3, main tbody')).map(el =>
+ el.tagName === 'H3' ? text(el) : readTable(el)
+ )
+ );
+ });
+
+ expect(text).toEqual([
+ 'Test Results (7 passed, 3 failed, 0 unsupported, 2 untestable)',
+ 'Control+Option+Space Results: 1 passed, 1 failed, 2 untestable',
+ [
+ ['MUST', "Role 'alert' is conveyed", 'Untestable'],
+ ['MUST', "Text 'Hello' is conveyed", 'Untestable'],
+ ['MUST', 'Severe negative side effects do not occur', 'Failed'],
+ ['SHOULD', 'Moderate negative side effects do not occur', 'Passed']
+ ],
+ 'Negative side effects of Control+Option+Space',
+ [
+ [
+ 'Output is excessively verbose, e.g., includes redundant and/or irrelevant speech',
+ 'anything',
+ 'SEVERE'
+ ]
+ ],
+ 'Space Results: 3 passed, 1 failed, 0 unsupported',
+ [
+ ['MUST', "Text 'Hello' is conveyed", 'Failed'],
+ ['MUST', "Role 'alert' is conveyed", 'Passed'],
+ ['MUST', 'Severe negative side effects do not occur', 'Passed'],
+ ['SHOULD', 'Moderate negative side effects do not occur', 'Passed']
+ ],
+ 'Enter Results: 3 passed, 1 failed, 0 unsupported',
+ [
+ ['MUST', "Text 'Hello' is conveyed", 'Failed'],
+ ['MUST', "Role 'alert' is conveyed", 'Passed'],
+ ['MUST', 'Severe negative side effects do not occur', 'Passed'],
+ ['SHOULD', 'Moderate negative side effects do not occur', 'Passed']
+ ]
+ ]);
+ });
+ });
+
it('inputs results and successfully submits', async () => {
await getPage(
{ role: 'tester', url: '/test-queue' },
diff --git a/client/tests/e2e/snapshots/saved/_account_settings.html b/client/tests/e2e/snapshots/saved/_account_settings.html
index 9842e5bd0..7d4d6c3bc 100644
--- a/client/tests/e2e/snapshots/saved/_account_settings.html
+++ b/client/tests/e2e/snapshots/saved/_account_settings.html
@@ -98,7 +98,7 @@ Admin Actions
Import Latest Test Plan Versions
- Date of latest test plan version: April 22, 2025 14:04 UTC
+ Date of latest test plan version: June 4, 2025 15:20 UTC
diff --git a/client/tests/e2e/snapshots/saved/_candidate-test-plan_24_1#summary.html b/client/tests/e2e/snapshots/saved/_candidate-test-plan_24_1#summary-assertions.html
similarity index 96%
rename from client/tests/e2e/snapshots/saved/_candidate-test-plan_24_1#summary.html
rename to client/tests/e2e/snapshots/saved/_candidate-test-plan_24_1#summary-assertions.html
index 00ccdb386..caba3bd8b 100644
--- a/client/tests/e2e/snapshots/saved/_candidate-test-plan_24_1#summary.html
+++ b/client/tests/e2e/snapshots/saved/_candidate-test-plan_24_1#summary-assertions.html
@@ -126,12 +126,25 @@
Open a Modal Dialog in reading mode
Space
SHOULD
- Other behaviors that create moderate
- negative impacts are not exhibited
+ Moderate negative side effects do not
+ occur
Other
diff --git a/client/tests/e2e/snapshots/saved/_candidate-test-plan_24_1.html b/client/tests/e2e/snapshots/saved/_candidate-test-plan_24_1.html
index 8e697757a..8cef24cd4 100644
--- a/client/tests/e2e/snapshots/saved/_candidate-test-plan_24_1.html
+++ b/client/tests/e2e/snapshots/saved/_candidate-test-plan_24_1.html
@@ -126,12 +126,25 @@
Open a Modal Dialog in reading mode
id="disclosure-btn-controls-candidateReviewRun-Test Results for Chrome"
aria-labelledby="disclosure-btn-candidateReviewRun-Test Results for Chrome">
Space Results: 6 passed, 0
@@ -572,23 +585,24 @@
MUST
- Other behaviors that create severe
- negative impacts are not exhibited
+ Severe negative side effects do not
+ occur
Passed
SHOULD
- Other behaviors that create moderate
- negative impacts are not exhibited
+ Moderate negative side effects do not
+ occur
Passed
- Other behaviors that create negative impact: None
+ The command 'Space' did not cause any negative
+ side effects.
Enter Results: 6 passed, 0
@@ -645,23 +659,24 @@
MUST
- Other behaviors that create severe
- negative impacts are not exhibited
+ Severe negative side effects do not
+ occur
Passed
SHOULD
- Other behaviors that create moderate
- negative impacts are not exhibited
+ Moderate negative side effects do not
+ occur
Passed
- Other behaviors that create negative impact: None
+ The command 'Enter' did not cause any negative
+ side effects.
Test Plan
-
+ Accordion
+
Action Menu Button Example Using aria-activedescendant
-
+
Action Menu Button Example Using element.focus()
- Alert Example
+ Alert Example
Banner Landmark
Breadcrumb Example
Checkbox Example (Mixed-State)
Checkbox Example (Two State)
- Color Viewer Slider
+ Color Viewer Slider
Combobox with Both List and Inline Autocomplete Example
- Command Button Example
+ Command Button Example
Complementary Landmark
Contentinfo Landmark
@@ -242,7 +243,7 @@
Date Picker Spin Button Example
-
+
Disclosure Navigation Menu Example
@@ -255,42 +256,41 @@
Link Example 1 (span element with text content)
-
+
Link Example 2 (img element with alt attribute)
-
+
Link Example 3 (CSS :before content property on a span
element)
Main Landmark
- Media Seek Slider
+ Media Seek Slider
Meter
- Modal Dialog Example
- Navigation Menu Button
-
+ Modal Dialog Example
+ Navigation Menu Button
+
Radio Group Example Using Roving tabindex
-
+
Radio Group Example Using aria-activedescendant
- Rating Radio Group
+ Rating Radio Group
Rating Slider
- Select Only Combobox Example
+ Select Only Combobox Example
Switch Example
Tabs with Manual Activation
- Toggle Button
- Vertical Temperature Slider
+ Toggle Button
+ Vertical Temperature Slider
Test Plan Version
-
- Feb 13, 2025 Menu button test plans: Change priority of
- assertion stateCollapsed from 1 to 2 (pull #1196)
- (bac64bf)
-
+ >
+ Versions in R&D or Deprecated
@@ -380,7 +380,7 @@ Test Plans Status Summary
data-testid="filter-all"
aria-pressed="true"
class="filter-button btn active btn-secondary">
- All Plans (36)
+ All Plans (37)
@@ -389,7 +389,7 @@ Test Plans Status Summary
data-testid="filter-rd"
aria-pressed="false"
class="filter-button btn btn-secondary">
- R&D Complete (32)
+ R&D Complete (33)
@@ -423,7 +423,7 @@ Test Plans Status Summary
@@ -573,7 +573,7 @@ Test Plans Status Summary
+
+ Accordion
+
+
+
+ JAWS , NVDA and VoiceOver for macOS
+
+
+
+
+
R&D
+
Complete Jun 4, 2025
+
+
+
+
+
+
+ Not Started
+
+
+ Not Started
+
+ None Yet
+
+
Test Plans Status Summary
None Yet
-
+
Banner Landmark
@@ -1366,7 +1418,7 @@ Test Plans Status Summary
None Yet
-
+
Breadcrumb Example Test Plans Status Summary
None Yet
-
+
Checkbox Example (Two State) Test Plans Status Summary
None Yet
-
+
Test Plans Status Summary
None Yet
-
+
Complementary Landmark Test Plans Status Summary
None Yet
-
+
Contentinfo Landmark Test Plans Status Summary
None Yet
-
+
Data Grid Example 1: Minimal Data Grid Test Plans Status Summary
None Yet
-
+
Date Picker Spin Button Example Test Plans Status Summary
None Yet
-
+
Disclosure Navigation Menu Example Test Plans Status Summary
None Yet
-
+
Test Plans Status Summary
None Yet
-
+
Editor Menubar Example Test Plans Status Summary
None Yet
-
+
Form Landmark
@@ -1965,7 +2017,7 @@ Test Plans Status Summary
None Yet
-
+
Horizontal Multi-Thumb Slider Test Plans Status Summary
None Yet
-
+
Link Example 1 (span element with text content) Test Plans Status Summary
None Yet
-
+
Link Example 2 (img element with alt attribute) Test Plans Status Summary
None Yet
-
+
Test Plans Status Summary
None Yet
-
+
Main Landmark
@@ -2236,7 +2288,7 @@ Test Plans Status Summary
None Yet
-
+
Media Seek Slider Test Plans Status Summary
None Yet
-
+
Meter
@@ -2342,7 +2394,7 @@ Test Plans Status Summary
None Yet
-
+
Navigation Menu Button Test Plans Status Summary
None Yet
-
+
Radio Group Example Using Roving tabindex Test Plans Status Summary
None Yet
-
+
Radio Group Example Using aria-activedescendant Test Plans Status Summary
None Yet
-
+
Rating Radio Group Test Plans Status Summary
None Yet
-
+
Rating Slider Test Plans Status Summary
None Yet
-
+
Switch Example
@@ -2664,7 +2716,7 @@ Test Plans Status Summary
None Yet
-
+
Tabs with Manual Activation Test Plans Status Summary
None Yet
-
+
Vertical Temperature Slider Test Plans Status Summary
Shift+X
SHOULD
+ Moderate negative side effects do not occur
+ Other
+
+
+
+
+
+ Summary of Negative Side Effects (0 severe, 1 moderate)
+
+
+
@@ -411,7 +440,7 @@
Test 1: Navigate forwards to a mixed checkbox in reading
- mode (28 passed, 0 failed, 0 unsupported)
+ mode (28 passed, 0 failed, 0 unsupported)
MUST
-
- Other behaviors that create severe negative impacts are
- not exhibited
-
+ Severe negative side effects do not occur
Passed
SHOULD
-
- Other behaviors that create moderate negative impacts are
- not exhibited
-
+ Moderate negative side effects do not occur
Passed
- Other behaviors that create negative impact: None
+ The command 'X' did not cause any negative side effects.
F Results: 7 passed, 0 failed, 0 unsupported
@@ -604,24 +627,18 @@
MUST
-
- Other behaviors that create severe negative impacts are
- not exhibited
-
+ Severe negative side effects do not occur
Passed
SHOULD
-
- Other behaviors that create moderate negative impacts are
- not exhibited
-
+ Moderate negative side effects do not occur
Passed
- Other behaviors that create negative impact: None
+ The command 'F' did not cause any negative side effects.
Tab Results: 7 passed, 0 failed, 0 unsupported
@@ -672,24 +689,18 @@
MUST
-
- Other behaviors that create severe negative impacts are
- not exhibited
-
+ Severe negative side effects do not occur
Passed
SHOULD
-
- Other behaviors that create moderate negative impacts are
- not exhibited
-
+ Moderate negative side effects do not occur
Passed
- Other behaviors that create negative impact: None
+ The command 'Tab' did not cause any negative side effects.