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

Add accessibility sniffs to pageslayout tests #6745

Merged
merged 2 commits into from
Apr 19, 2023

Conversation

skyvine
Copy link
Contributor

@skyvine skyvine commented Feb 8, 2023

Status

Ready for review

Description of Changes

This commit follows the structure of the current pageslayout tests by collecting info in the save_screenshot_and_html function, with the intent that it will later be consumed in the appropriate context. Presumably, a new make target similar to validate-test-html will be added, or this target will be updated.

Notes for Reviewers

As previously discussed with @cfm, this commit adds code in Dockerfiles to install software from from npm (context). This toolchain is new to the project.

Testing

How should the reviewer test this PR?
Write out any special testing steps here.

make test TESTFILES=tests/functional/pageslayout will exercise all of the changes to the test suite. A summary of the results can be seen with make accessibility-summary, which will process raw output into a summary file and open it in less. Full output can be found at tests/functional/pageslayout/accessibility-info/. The directory structure is similar to that of tests/functional/pageslayout/html, but after the locale there are 2 separate directories, errors and reviews, with the corresponding output saved to files using the test_name parameter.

The summary file reports when the sniff output differs by locale (this is only approximate but I believe it to be reasonably accurate, see the implementation of summarize_accessibility_results for details) . Currently, there are no differences by locale. I exercised this code path by manually editing the text files created in the make test invocation to simulate differences. Just delete any line starting with MessageType in any file to see this in action.

Deployment

N/A

Checklist

If you made changes to the server application code:

  • Linting (make lint) and tests (make test) pass in the development container

If you made changes to securedrop-admin:

N/A

If you made changes to the system configuration:

N/A

If you added or removed a file deployed with the application:

N/A

If you made non-trivial code changes:

Test suite question is N/A, this is a change to the test suite itself.

Choose one of the following:

Based on the link to freedomofpress/securedrop-docs#245 added by @cfm, it looks like there does need to be a doc update here?

  • I have opened a PR in the docs repo for these changes, or will do so later
  • I would appreciate help with the documentation
  • These changes do not require documentation

If you added or updated a production code dependency:

I previously listed this as N/A because the dependency is not "production" in the sense that it does not need to be deployed to users, is that understanding accurate?

@skyvine skyvine requested a review from a team as a code owner February 8, 2023 21:43
@skyvine skyvine mentioned this pull request Feb 18, 2023
4 tasks
@cfm
Copy link
Member

cfm commented Feb 22, 2023

@saffronsnail, thanks for checking in about this. Since your changes here are still WIP, this is not a full review, but I'm happy to discuss your questions so far.

I am trying to match the structure of the current pageslayout tests. Towards this goal, the sniffing function creates a set of output files at tests/functional/pageslayout/accessibility-info. For the pageslayout tests, data saved in similar directories is just static captures of the page, and make validate-test-html is used to view the output of the validator. In this commit, the data saved is the output of the tests itself. Is this the best place to save it? I could also add it to a new directory under test-results, which appears in the root of the repository after running make test.

On a similar note, the output was written under my original assumption that this would be directly displayed to the user. However, since this is being saved to a file I wonder if it would be more useful to save it in a structured format; I see that there are already some XML files in the test-results directory. Would it be useful to have a separate make command to display a summary of the errors/reviews generated for each page?

The page-layout tests report via pytest, which we've configured to save to test-results/junit.xml for CircleCI to parse. It doesn't seem like we'll be able to preserve that association for these tests, so I agree that make validate-test-html is the better precedent: save the results along with the page-layout captures; display them at the end; and (eventually) fail on errors.

There are also a few organizational/style things. Much of the new code is in the new tests/functional/pageslayout/accessibility.py file. I considered adding it to the utils.py file in the same directory, but held off because that would triple the size of the file and there is only one point where the code needs to interoperate (more on that later). Is there any preference for this to be in a separate file or merged with utils?

I like your approach here, since the new tests.functional.pageslayout.accessibility module corresponds directly to the new html_codesniffer dependency.

I added the call to the sniffing function inside of save_screenshot_and_html because it needs the same data and avoids adding a bunch of one-line changes to virtually every function currently in the pageslayout directory. I feel like this is a little misleading to the reader, because there's no indication that save_screenshot_and_html contains accessibility-related functionality. Thoughts on renaming this to save_accessibility_info_screenshot_and_html? I'm wary of making the identifier too long, so a more generic name like save_static_data might make sense. I could leave it as-is. Or add the calls separately, as I originally intended.

save_static_data() sounds like a good refactoring, in a separate commit and documented with a docstring. :-)

@cfm cfm marked this pull request as draft February 22, 2023 00:14
@skyvine skyvine marked this pull request as ready for review February 26, 2023 23:39
@skyvine
Copy link
Contributor Author

skyvine commented Feb 26, 2023

Thanks for the feedback! I think the new version of the branch should be good to go, I've also updated the first comment to reflect the current status. One thing that I noticed when updating the function name is several comments along the lines of:

        # The documentation uses an identical screenshot with a different name:
        # https://github.com/freedomofpress/securedrop-docs/blob/main/docs/images/manual
        # /screenshots/journalist-code-fail_login_many.png
        # So we take the same screenshot again here
        # TODO(AD): Update the documentation to use a single screenshot

The function name doesn't explicitly say that it's taking a screenshot any more. I don't think this is a big problem especially with the docstring update, but just wanted to call attention to it in case it is more concerning than I assume.

Also, the summary includes the full path to the accessibility-info directory at the top as a helpful notice. I'm not sure if this is too helpful - this reveals the developer's username if the summary file becomes public. This file is only generated as a result of make accessibility-summary, which I would not expect to be used by any CI process. Also, I'm not sure if it actually reveals the username, the account I'm using for preparing this pull request is named "user" and it might be that the development container also uses the name "user" regardless of my account's username, and it's just a coincidence that it's the same. Let me know if this is a concern and I can look more into it, or just modify the line to print the path relative to the root of the repository rather than the absolute path.

Finally, the circle-ci doc mentions that, "[w]hen you save test data using the store_test_results step, CircleCI collects data from XML files and uses it to provide insights into your job." IIUC, this means that if I updated the code to write out XML files then it could actually be integrated with circle-ci. Would there be any interest in a change that implements this on your end? If so, I still think that this should be merge-able as-is and XML output can be handled in a separate PR.

@skyvine
Copy link
Contributor Author

skyvine commented Feb 26, 2023

Here's a copy of the summary output on my machine in case it is of interest:

Accessibility Summary

journalist-account_edit_hotp_secret.txt:
errors: 1
reviews: 60

journalist-account_new_two_factor_hotp.txt:
errors: 1
reviews: 61

journalist-account_new_two_factor_totp.txt:
errors: 2
reviews: 60

journalist-admin.txt:
errors: 2
reviews: 70

journalist-admin_add_user_hotp.txt:
errors: 1
reviews: 76

journalist-admin_add_user_totp.txt:
errors: 1
reviews: 76

journalist-admin_changes_logo_image.txt:
errors: 4
reviews: 99

journalist-admin_edit_hotp_secret.txt:
errors: 4
reviews: 101

journalist-admin_edit_totp_secret.txt:
errors: 2
reviews: 60

journalist-admin_index_no_documents.txt:
errors: 0
reviews: 49

journalist-admin_interface_index.txt:
errors: 2
reviews: 67

journalist-admin_new_user_two_factor_hotp.txt:
errors: 1
reviews: 60

journalist-admin_new_user_two_factor_totp.txt:
errors: 2
reviews: 60

journalist-admin_ossec_alert_button.txt:
errors: 4
reviews: 99

journalist-admin_system_config_page.txt:
errors: 4
reviews: 98

journalist-clicks_on_source_and_selects_documents.txt:
errors: 8
reviews: 107

journalist-code-fail_login.txt:
errors: 1
reviews: 64

journalist-code-fail_login_many.txt:
errors: 1
reviews: 64

journalist-code-fail_to_visit_admin.txt:
errors: 1
reviews: 63

journalist-col.txt:
errors: 8
reviews: 107

journalist-col_has_no_key.txt:
errors: 7
reviews: 94

journalist-col_javascript.txt:
errors: 8
reviews: 107

journalist-col_no_document.txt:
errors: 4
reviews: 84

journalist-composes_reply.txt:
errors: 8
reviews: 107

journalist-delete_all.txt:
errors: 4
reviews: 84

journalist-delete_all_confirmation.txt:
errors: 13
reviews: 108

journalist-delete_none.txt:
errors: 8
reviews: 109

journalist-delete_one.txt:
errors: 4
reviews: 84

journalist-delete_one_confirmation.txt:
errors: 13
reviews: 108

journalist-edit_account_admin.txt:
errors: 4
reviews: 101

journalist-edit_account_user.txt:
errors: 4
reviews: 97

journalist-index.txt:
errors: 9
reviews: 88

journalist-index_javascript.txt:
errors: 9
reviews: 88

journalist-index_no_documents.txt:
errors: 0
reviews: 49

journalist-index_with_text.txt:
errors: 1
reviews: 63

journalist-login.txt:
errors: 1
reviews: 63

source-checks_for_reply.txt:
errors: 7
reviews: 94

source-deletes_reply.txt:
errors: 8
reviews: 69

source-enter-codename-in-login.txt:
errors: 3
reviews: 57

source-generate.txt:
errors: 2
reviews: 58

source-index.txt:
errors: 3
reviews: 65

source-login.txt:
errors: 3
reviews: 57

source-logout_page.txt:
errors: 2
reviews: 49

source-lookup-shows-codename.txt:
errors: 7
reviews: 70

source-lookup.txt:
errors: 8
reviews: 71

source-next_submission_flashed_message.txt:
errors: 8
reviews: 71

source-notfound.txt:
errors: 2
reviews: 47

source-session_timeout.txt:
errors: 4
reviews: 67

source-submission_entered_text.txt:
errors: 7
reviews: 70

source-tor2web_warning.txt:
errors: 1
reviews: 50

source-use_tor_browser.txt:
errors: 1
reviews: 47

source-why_journalist_key.txt:
errors: 4
reviews: 49

Copy link
Member

@cfm cfm left a comment

Choose a reason for hiding this comment

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

Very nice, @saffronsnail! I've tested both stages of this linting locally:

  1. make test TESTFILES=securedrop/tests/functional/pageslayout
  2. make accessibility-summary

I do want to follow up on your original idea:

Presumably, a new make target similar to validate-test-html will be added, or this target will be updated.

How do you envision this linting being used for reporting in CI? You say that you don't expect make accessibility-summary to be used this way. But I'm not sure what the alternative is: until we've squashed the most-recurrent errors, displaying (never mind erroring on) the per-test output won't be useful. As you note re: CircleCI's store_test_results step, one option is to write a custom test-results/junit.xml with the per-test output, but we still won't want to consider these errors as failures yet.

Makefile Outdated Show resolved Hide resolved
@skyvine
Copy link
Contributor Author

skyvine commented Mar 8, 2023

Ah, there was a misunderstanding. I thought that make validate-test-html was intended for interactive use only, but I now see that it is used in .circleci/config.yml. I thought that the output needed to be parseable by CircleCI (eg, XML output) in order to be used at all, but it looks like this is incorrect. I added a new commit that updates .circleci/config.yml to run make accessibility-summary immediately after make validate-test-html. Let me know if this would be better done in a separate target, I assume that I'd basically be copy/pasting the app-page-layout-tests target and replacing the make command.

@skyvine
Copy link
Contributor Author

skyvine commented Mar 8, 2023

As before, I can sqaush these commits once the complete changeset looks good.

@skyvine
Copy link
Contributor Author

skyvine commented Mar 8, 2023

Looking at the circleci output, I have a couple of questions.

First, is it expected that there is only output for 5 files? I'm assuming that the layout tests are running on a subset of files to keep it more manageable while working towards validatability, since the validate-test-html output also mentions only those files, but I want to confirm in case there is a problem with my commit.

Second, I don't see a way to go to the files containing the full output in circleci. I wrote the target that way with an implicit assumption that this is running locally (I haven't worked with devops tools very much 😅), so I'm thinking it might be better to have the make target print the full output after (or, possibly, instead of) the summary. Thoughts?

@skyvine
Copy link
Contributor Author

skyvine commented Mar 18, 2023

Hey @cfm, I'm not sure if this needs more active work or not based on the way it looks in CircleCI. As previously implied, this aspect of the CL was not fully considered before. My main point of uncertainty is about the developer's workflow, in order to determine whether or not the current CircleCI output is sufficient. If developers typically run the test suite locally, and CircleCI is just used for communicating results between people, then I expect the current CL will work fine because the developer can examine the files locally. But if developers typically use CircleCI as the mechanism for running the test suite, then it is certainly not sufficient.

In case it is not sufficient, I will mark this as "Work in Progress" again since more work needs to be done. My plan is to write out json files instead of plain text files, and invert the page/error relationship in reporting (eg, "this error occurs in these locations of these pages" instead of "this page contains these errors in these locations) in order to minimize the amount of text that y'all will have to go through. I think this will also make it clearer what amount of impact fixing each error will have, when there is a common cause. But we'll see how it looks when it's actually implemented.

Please let me know what you think whenever you have a moment!

@cfm
Copy link
Member

cfm commented Mar 21, 2023

Thanks for following up, @saffronsnail. From #6745 (comment):

First, is it expected that there is only output for 5 files? I'm assuming that the layout tests are running on a subset of files to keep it more manageable while working towards validatability, since the validate-test-html output also mentions only those files, but I want to confirm in case there is a problem with my commit.

Good question! This behavior isn't caused by anything in your code. The key lines are:

parallelism: 5

RUN_TESTFILES=$(echo "$JOB_TESTFILES" |circleci tests split --split-by=timings)

Together, these mean that the app-page-layout-tests job is split across 5 runners, each of which is running a subset of the tests in the test suite balanced according to their historical timings. Since the validation steps run on the output of the page-layout tests, both the existing make test-validate-html and your make accessibility-summary jobs have access only to the output of the tests run by a given runner.

For this reason, although some developers and contributors may see these results only in CI and not locally, your current implementation makes sense. While you could "write out json files instead of plain text files, and invert the page/error relationship in reporting (eg, "this error occurs in these locations of these pages" instead of "this page contains these errors in these locations)", in CI that summary will still be only per-runner rather than global. And that's fine. :-)

Does this make sense? If you're happy with the current state of this branch, I'll plan to review it on Monday.

@skyvine
Copy link
Contributor Author

skyvine commented Mar 23, 2023

That makes sense! And in that case, I think the branch is fine as-is. Whenever you have the resources to review the CL works for me, I just wanted to make sure I knew if there was anything actionable on my end. =)

Copy link
Member

@cfm cfm left a comment

Choose a reason for hiding this comment

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

This looks great, @saffronsnail! CI is currently failing from your fork. That's definitely not your fault, since the actual tests are passing. I think it's a side effect of #6739; I'm looking into it.

Testing: Since CI splits test results, I've tested locally with TESTFILES=tests/functional/pageslayout make test && make accessibility-summary, which reported on the entire test suite.

Code review: You've designed this nicely, given (a) how complex our test suite is anyway and (b) injecting html_codesniffer during each functional test. I've given some thought to whether "sniff" is a confusing concept and term to introduce in addition to "lint" and "validate". I think it's fine: it's specific to html_codesniffer, and it is different than static linting and validation.

I've left requested two changes inline; please review them when you get a chance. Tomorrow I'll ask the rest of the team for feedback on the introduction of npm and html_codesniffer in these Dockerfiles and get back to you.

.circleci/config.yml Show resolved Hide resolved
securedrop/tests/functional/pageslayout/accessibility.py Outdated Show resolved Hide resolved
@skyvine
Copy link
Contributor Author

skyvine commented Mar 28, 2023

Thanks @cfm ! I implemented both of the recommended changes in a single new commit. Hopefully npm and html_codesniffer don't cause any issues!

Copy link
Member

@cfm cfm left a comment

Choose a reason for hiding this comment

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

Thank for these changes, @saffronsnail. Please see my new review here, primarily re: npm.

I'm offline for the rest of this week, so I'll revisit this next Monday.

.circleci/config.yml Outdated Show resolved Hide resolved
devops/docker/CIDockerfile Outdated Show resolved Hide resolved
securedrop/dockerfiles/focal/python3/Dockerfile Outdated Show resolved Hide resolved
@skyvine
Copy link
Contributor Author

skyvine commented Apr 8, 2023

I uploaded a commit that technically uses npm ci successfully, but I don't think it's the version that we want to push based on the level of concern you have about using the npm toolchain. The issue is that I don't see a way to use the command in the Dockerfile, so it's used in the Makefile, which means the command would run every time anybody runs make test, instead of baking it into the image. Due to this concern, which I will explain in more detail below, I have labelled the commit as "not for production" and I have not signed it.

The primary issue with running it in the Dockerfile is that, if I'm understanding Docker correctly, we don't actually have the repository available. In securedrop/bin/dev-shell, the docker run invocation passes in --volume "${TOPLEVEL}:${TOPLEVEL}:Z", which creates a bind mount to the local copy of the repository. This is why the output files from the test suite are visible outside of the Docker container. But it also means that the package.json and package-lock.json are unavailable until we call docker run. Additionally, this means that the files are not installed into the docker image directly, but into the developer's system (albeit in the source tree, not in some globally referenced directory). I expect that this would raise some concerns on your end.

I could add the html-codesniffer package itself into the repository. The full cloc output of the node-modules directory shows that it includes > 10,000 lines of javascript, but I might be able to get away with just committing the HTMLCS.js file without all the other stuff. I believe this is true because the test code just reads the file and sends the contents to the browser, so I don't see how it could be pulling in any of the other files in that directory. It seems to be self-contained. This would mean adding ~350 lines of javascript. I'm not sure how to prepare this commit in a way that is compliant with their license; there is a notice at the top about the software being subject to the terms of their license.txt file, which looks like a fairly normal BSD-style license. Presumably I could just include a copy of that in the commit as well, and make sure that both are in a separate directory. However, I am not a lawyer and I do not know if this would be sufficient, or if it would work for the SecureDrop project. Additionally, this method would increase the maintenance burden because a new copy of the built file would have to be added instead of having commits that essentially just increment a bunch of version numbers.

If adding HTMLCS.js directly to the repository works on your end, I'll start working on that. Otherwise, I'm open to ideas.

cfm added a commit to skyvine/securedrop-clone that referenced this pull request Apr 10, 2023
As a true (non-phony) Make target, "node_modules" will be refreshed via
"npm ci" only when "package-lock.json" has changed.  That makes it a
one-time operation (like "make venv" and equivalents) for developers and
steps around the fact that "package-lock.json" is not available[1] at
the containers' build-time.

[1]: freedomofpress#6745 (comment)
@cfm
Copy link
Member

cfm commented Apr 11, 2023

Thanks for this proof-of-concept, @saffronsnail, and for walking me through your thinking. I think we can solve this dilemma:

The primary issue with running it in the Dockerfile is that, if I'm understanding Docker correctly, we don't actually have the repository available. In securedrop/bin/dev-shell, the docker run invocation passes in --volume "${TOPLEVEL}:${TOPLEVEL}:Z", which creates a bind mount to the local copy of the repository. This is why the output files from the test suite are visible outside of the Docker container. But it also means that the package.json and package-lock.json are unavailable until we call docker run.

Good catch. In the main Dockerfile, I'm reminded that we already resort to indirection to work around this problem when we want to verify packages signed with third-party keys, since we can't just include the keys directly in the repository. For example:

# Import Tor release signing key
ENV TOR_RELEASE_KEY_FINGERPRINT "EF6E286DDA85EA2A4BA7DE684E2C6E8793298290"
RUN curl -s https://openpgpkey.torproject.org/.well-known/openpgpkey/torproject.org/hu/kounek7zrdx745qydx6p59t9mqjpuhdf | gpg2 --import -

We could do the same thing here, by embedding the desired package-lock.json in the Dockerfile to make it available to npm ci. But that's both overkill and not very extensible, so I think we can keep it simpler with something like a650874.

Additionally, this means that the files are not installed into the docker image directly, but into the developer's system (albeit in the source tree, not in some globally referenced directory). I expect that this would raise some concerns on your end.

Our main concern isn't whether the files themselves are available outside the container; this is true of the Python virtual environment too. We just want to make sure that npm ci is running against a package-lock.json with pinned package hashes, which this provides. :-)

What do you think, @saffronsnail? @legoktm, if you have time, I'd appreciate your sign-off too.

@legoktm
Copy link
Member

legoktm commented Apr 11, 2023

Sorry, I feel really silly now, I didn't realize that html_codesniffer has no dependencies! Locking is pretty useless then (small benefit in that it pins sha256 checksum but that's pretty negligible). Given that, I think your original npm install --global html_codesniffer is fine for now. (I would like to split up the docker image as a whole, it's way too big with too many things, but that's an entirely separate issue we shouldn't tackle in this PR)

Accessibility sniffs going to be added to this function, meaning that
the old name is no longer complete. Specifying each thing separately
would make the name unwieldy, so change it to a generic name and add a
docstring explicating the things it does.
@skyvine
Copy link
Contributor Author

skyvine commented Apr 18, 2023

Ok, if I understood everything correctly then I think the new state of the PR should be mergable (assuming CircleCI is happy). I went back to the version that used npm install --global and squashed everything down to 2 commits, the renaming and the addition of the new tests.

I did add a new line to the Dockerfiles:

npm view html_codesniffer 2>/dev/null | head -n 2 | cut -d'|' -f 3 | grep none

This line runs before npm install --global, and will return a non-zero exit code if html_codesniffer has any dependencies added to it down the line. I confirmed that this causes the docker build to fail by repacing html_codesniffer with inquirer, a random package that has 15 dependencies at time of writing. For context, the output of view | head gives:


html_codesniffer@2.5.1 | BSD-3-Clause | deps: none | versions: 7

When there are dependencies, the word none will be replaced with a number. I redirect the error output of view into /dev/null because view gets understandably upset when the stream it's writing to is closed out from under it. If there is some other error like the package being pulled off of the repo, then I expect that will be apparent because the view command will not produce the expected text onto standard output.

Copy link
Member

@cfm cfm left a comment

Choose a reason for hiding this comment

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

This is looking really good, @saffronsnail. I've requested one change inline, with which I'll be ready to merge this this week. :-)

devops/docker/CIDockerfile Outdated Show resolved Hide resolved
This commit follows the structure of the current pageslayout tests by
collecting info in the `save_screenshot_and_html` function, and saving
the results into a new subdirectory under
`tests/functional/pageslayout/accessibility-info`. Under this there is a
layer of subdirectories for the locale, then a second layer of
subdirectories to organize messages between errors that require human
review and errors that are machine-verified.

Additionally, this adds a new make target `make accessibility-summary`
which will process all of these data files and display a summary. This
is done rather than displaying the full output because there are
currently ~50,000 lines of output stored in these files; if we displayed
only errors then this would cut it down to 2,600 lines which is a
significant reduction, but still a lot for terminal output. Note that
many of the messages are duplicates across pages. For example, the blue
buttons with white text are slightly below the contrast guideline, and
there is a message about this problem on nearly every page.

This commit also adds html_codesniffer as a dependency to the project.
This is installed globally into the docker image because
html_codesniffer itself requires no dependencies. See PR 6745 for
the full discussion.

Co-authored-by: Cory Francis Myers <cfm@panix.com>
Copy link
Member

@cfm cfm left a comment

Choose a reason for hiding this comment

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

All changes made and all looks good to me, @saffronsnail. Thanks for implementing this—and thanks very much for your patience with this long review process. We appreciate it!

@cfm cfm changed the title (WIP) Add accessibility sniffs to pageslayout tests Add accessibility sniffs to pageslayout tests Apr 19, 2023
@cfm cfm merged commit d01e743 into freedomofpress:develop Apr 19, 2023
cfm added a commit that referenced this pull request Jun 23, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
3 participants