with_clues 1.2.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 270e2b6f2af29d8d7871cba1103db16ec84efd97380acb4592b094729adfb994
4
- data.tar.gz: 22de0039993571e0a491cbe9ec5d1b53021d184793d99bfc47ffb34d64ca833a
3
+ metadata.gz: b17d4876d0162d52c3cf3e767c70b26cb2d18ff9ed144eda164ddf3afdbcd6f7
4
+ data.tar.gz: 6e605bc07cae10efaf1b3866987f31cc36bd4bf619b5414b8d43d0f8c7b0c76a
5
5
  SHA512:
6
- metadata.gz: f019bd0a5354f762b4a7304d88509054fe53078528c188e8186aa8af134ff40d99042b0c3556ea2c2497317e0d899e764a6649e78f22ed1aea53e5301ee83c6f
7
- data.tar.gz: d106562538a1cd625d422e1e500aa34564c5f1c97bfeea30fa6083c7e7f1b99d4addf59f7046c29575122f0b43a15c61b7efefc6fba16ef8a0aeec0107b15148
6
+ metadata.gz: 104eb1b602cd79c0dd3183daf5e47dff90e39bbeed82c06209b079902bc7e27d5a49631a22bc76c2a801a9193d3f8d8e0f026755962a2688fa135fb73aa9b426
7
+ data.tar.gz: 9c8bf899641fe07713725bd7ed7591ff85e59ad2e61c05b508ecddfc71f3a9c352132ed685e0f0cb3d16b304403c18f039626fa0a5e153d51002161ab66edab4
data/Dockerfile.dx ADDED
@@ -0,0 +1,28 @@
1
+ ARG RUBY_VERSION
2
+ FROM ruby:${RUBY_VERSION}
3
+
4
+ ENV DEBIAN_FRONTEND noninteractive
5
+ RUN apt-get -y update
6
+
7
+
8
+ # dx.snippet.start=templates/snippets/bundler/latest__bundler.dockerfile-snippet
9
+ # Based on documentation at https://guides.rubygems.org/command-reference/#gem-update
10
+ # based on the vendor's documentation
11
+ RUN echo "gem: --no-document" >> ~/.gemrc && \
12
+ gem update --system && \
13
+ gem install bundler
14
+
15
+ # dx.snippet.end=templates/snippets/bundler/latest__bundler.dockerfile-snippet
16
+
17
+
18
+ # dx.snippet.start=templates/snippets/vim/bullseye_vim.dockerfile-snippet
19
+ # Based on documentation at https://packages.debian.org/search?keywords=vim
20
+ # based on the vendor's documentation
21
+ ENV EDITOR=vim
22
+ RUN apt-get install -y vim && \
23
+ echo "set -o vi" >> /root/.bashrc
24
+ # dx.snippet.end=templates/snippets/vim/bullseye_vim.dockerfile-snippet
25
+
26
+ # This entrypoint produces a nice help message and waits around for you to do
27
+ # something with the container.
28
+ COPY dx/show-help-in-app-container-then-wait.sh /root
data/README.md CHANGED
@@ -6,8 +6,7 @@ Suppose you have this:
6
6
  expect(page).to have_content("My Awesome Site")
7
7
  ```
8
8
 
9
- And Capybara says that that content is not there and that is all it says. You might slap in a `puts page.html` and try again.
10
- Instead, what if you could not do that and do this?
9
+ And Capybara says that that content is not there and that is all it says. You might slap in a `puts page.html` and try again. Instead, what if you could not do that and do this?
11
10
 
12
11
  ```ruby
13
12
  with_clues do
@@ -71,8 +70,7 @@ end
71
70
 
72
71
  ## Use
73
72
 
74
- In general, you would not want to wrap all tests with `with_clues`. This is a diagnostic tool to allow you to get more
75
- information on a test that is failing. As such, your workflow might be:
73
+ In general, you would not want to wrap all tests with `with_clues`. This is a diagnostic tool to allow you to get more information on a test that is failing. As such, your workflow might be:
76
74
 
77
75
  1. Notice a test failing that you cannot easily diagnose
78
76
  1. Wrap the failing assertion in `with_clues`:
@@ -89,8 +87,10 @@ information on a test that is failing. As such, your workflow might be:
89
87
 
90
88
  There are three clues included:
91
89
 
92
- * Dumping HTML - when `page` exists, it will dump the contents of `page.html` when the test fails
93
- * Dumping Browser logs - for a browser-based test, it will dump anything that was `console.log`'ed
90
+ * Dumping HTML - when `page` exists, it will dump the contents of `page.html` (for Selenium) or `page.content`
91
+ (for Playwright) when the test fails
92
+ * Dumping Browser logs - for a browser-based test, it will dump anything that was `console.log`'ed. This should
93
+ work with Selenium and Playwright
94
94
  * Arbitrary context you pass in, for example when testing an Active Record
95
95
 
96
96
  ```ruby
@@ -107,15 +107,16 @@ There are three clues included:
107
107
  `with_clues` is intended as a diagnostic tool you can develop and enhance over time. As your team writes more code or develops
108
108
  more conventions, you can develop diagnostics as well.
109
109
 
110
- To add one, create a class that implements `dump(notifier, context:)` or `dump(notifier, context:, page:)`:
110
+ To add one, create a class that implements `dump(notifier, context:)` or `dump(notifier, context:, page:)` or
111
+ `dump(notifier, context:, page:, captured_logs)`:
111
112
 
112
- * `notifier` is a `WithClues::Notifier` that you should use to produce output:
113
- * `notify` - output text, preceded with `[ with_clues ]` (this is so you can tell output from your code vs from `with_clues`)
114
- * `blank_line` - a blank line (no prefix)
115
- * `notify_raw` - output text without a prefix, useful for removing ambiguity about what is being output
113
+ * `notifier` is a `WithClues::Notifier` that you should use to produce output via the following methods:
114
+ * `notifier.notify` - output text, preceded with `[ with_clues ]` (this is so you can tell output from your code vs from `with_clues`)
115
+ * `notifier.blank_line` - a blank line (no prefix)
116
+ * `notifier.notify_raw` - output text without a prefix, useful for removing ambiguity about what is being output
116
117
  * `context:` the context passed into `with_clues` (nil if it was omitted)
117
- * `page:` If `dump` requires this keyword, your clue will only be used in a browser context when the Capybara `page` object is available.
118
- In that case, that is what is passed in.
118
+ * `page:` will be given the Selenium or Playwright page object
119
+ * `captured_logs:` for Playwright, this will be the browser console logs captured inside the block
119
120
 
120
121
  For example, suppose you want to output information about an Active Record like so:
121
122
 
data/bin/matrix ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env bash
2
+
3
+ . dx/docker-compose.env
4
+ for version in ${RUBY_VERSIONS[@]}; do
5
+ dx/exec -v $version bin/setup && dx/exec -v $version bin/ci
6
+ done
@@ -0,0 +1,33 @@
1
+ # THIS IS GENERATED - DO NOT EDIT
2
+
3
+ services:
4
+ with-clues-3.1:
5
+ image: sustainable-rails/with-clues-dev:ruby-3.1
6
+ init: true
7
+ volumes:
8
+ - type: bind
9
+ source: "./"
10
+ target: "/root/work"
11
+ consistency: "consistent"
12
+ entrypoint: /root/show-help-in-app-container-then-wait.sh
13
+ working_dir: /root/work
14
+ with-clues-3.2:
15
+ image: sustainable-rails/with-clues-dev:ruby-3.2
16
+ init: true
17
+ volumes:
18
+ - type: bind
19
+ source: "./"
20
+ target: "/root/work"
21
+ consistency: "consistent"
22
+ entrypoint: /root/show-help-in-app-container-then-wait.sh
23
+ working_dir: /root/work
24
+ with-clues-3.3:
25
+ image: sustainable-rails/with-clues-dev:ruby-3.3
26
+ init: true
27
+ volumes:
28
+ - type: bind
29
+ source: "./"
30
+ target: "/root/work"
31
+ consistency: "consistent"
32
+ entrypoint: /root/show-help-in-app-container-then-wait.sh
33
+ working_dir: /root/work
data/dx/build ADDED
@@ -0,0 +1,60 @@
1
+ #!/bin/bash
2
+
3
+ set -e
4
+
5
+ SCRIPT_DIR=$( cd -- "$( dirname -- "${0}" )" > /dev/null 2>&1 && pwd )
6
+
7
+ . "${SCRIPT_DIR}/dx.sh.lib"
8
+
9
+ require_command "docker"
10
+ load_docker_compose_env
11
+
12
+ usage_on_help "Builds the Docker image based on the Dockerfile" "" "build.pre" "build.post" "${@}"
13
+
14
+ for ruby_version in ${RUBY_VERSIONS[@]}; do
15
+ dockerfile="Dockerfile.dx"
16
+ docker_image_name="${IMAGE}:ruby-${ruby_version}"
17
+
18
+ log "Building for Ruby '${ruby_version}' using Docker image name '${docker_image_name}'"
19
+
20
+ exec_hook_if_exists "build.pre" "${dockerfile}" "${docker_image_name}"
21
+
22
+ docker build \
23
+ --file "${dockerfile}" \
24
+ --build-arg="RUBY_VERSION=${ruby_version}" \
25
+ --tag "${docker_image_name}" \
26
+ ./
27
+
28
+ exec_hook_if_exists "build.post" "${dockerfile}" "${docker_image_name}"
29
+ log "🌈" "Your Docker image has been built tagged '${docker_image_name}'"
30
+ done
31
+
32
+ log "✅" "All images built"
33
+
34
+ log "✨" "Creating docker-compose.dx.yml"
35
+ compose_file="docker-compose.dx.yml"
36
+ log "🗑️" "Deleting previous ${compose_file}"
37
+
38
+ rm -f "${compose_file}"
39
+ echo "# THIS IS GENERATED - DO NOT EDIT" > "${compose_file}"
40
+ echo "" >> "${compose_file}"
41
+ echo "services:" >> "${compose_file}"
42
+
43
+ for ruby_version in ${RUBY_VERSIONS[@]}; do
44
+ log "Generating stanza for version '${ruby_version}'"
45
+ docker_image_name="${IMAGE}:ruby-${ruby_version}"
46
+ echo " with-clues-${ruby_version}:" >> "${compose_file}"
47
+ echo " image: ${docker_image_name}" >> "${compose_file}"
48
+ echo " init: true" >> "${compose_file}"
49
+ echo " volumes:" >> "${compose_file}"
50
+ echo " - type: bind" >> "${compose_file}"
51
+ echo " source: \"./\"" >> "${compose_file}"
52
+ echo " target: \"/root/work\"" >> "${compose_file}"
53
+ echo " consistency: \"consistent\"" >> "${compose_file}"
54
+ echo " entrypoint: /root/show-help-in-app-container-then-wait.sh" >> "${compose_file}"
55
+ echo " working_dir: /root/work" >> "${compose_file}"
56
+ done
57
+ log "🎼" "${compose_file} is now created"
58
+ log "🔄" "You can run dx/start to start it up, though you may need to stop it first with Ctrl-C"
59
+
60
+ # vim: ft=bash
@@ -0,0 +1,5 @@
1
+ # This array must include the oldest Ruby first!
2
+ RUBY_VERSIONS=("3.1" "3.2" "3.3")
3
+ IMAGE=sustainable-rails/with-clues-dev
4
+ PROJECT_NAME=with-clues
5
+ # vim: ft=bash
data/dx/dx.sh.lib ADDED
@@ -0,0 +1,24 @@
1
+ # shellcheck shell=bash
2
+
3
+ . "${SCRIPT_DIR}/setupkit.sh.lib"
4
+
5
+ require_command "realpath"
6
+ require_command "cat"
7
+
8
+ ENV_FILE=$(realpath "${SCRIPT_DIR}")/docker-compose.env
9
+
10
+ load_docker_compose_env() {
11
+ . "${ENV_FILE}"
12
+ }
13
+
14
+ exec_hook_if_exists() {
15
+ script_name=$1
16
+ shift
17
+ if [ -x "${SCRIPT_DIR}"/"${script_name}" ]; then
18
+ log "🪝" "${script_name} exists - executing"
19
+ "${SCRIPT_DIR}"/"${script_name}" "${@}"
20
+ else
21
+ debug "${script_name} does not exist"
22
+ fi
23
+ }
24
+ # vim: ft=bash
data/dx/exec ADDED
@@ -0,0 +1,59 @@
1
+ #!/bin/bash
2
+
3
+ set -e
4
+
5
+ SCRIPT_DIR=$( cd -- "$( dirname -- "${0}" )" > /dev/null 2>&1 && pwd )
6
+
7
+ . "${SCRIPT_DIR}/dx.sh.lib"
8
+
9
+ require_command "docker"
10
+ load_docker_compose_env
11
+
12
+ usage_description="Execute a command inside the app's container."
13
+ usage_args="[-s service] [-v ruby_version] command"
14
+ usage_pre="exec.pre"
15
+ usage_on_help "${usage_description}" "${usage_args}" "${usage_pre}" "" "${@}"
16
+
17
+ LATEST_RUBY=${RUBY_VERSIONS[0]}
18
+ DEFAULT_SERVICE=with-clues-${LATEST_RUBY}
19
+ SERVICE="${SERVICE_NAME:-${DEFAULT_SERVICE}}"
20
+ while getopts "v:s:" opt "${@}"; do
21
+ case ${opt} in
22
+ v )
23
+ SERVICE="with-clues-${OPTARG}"
24
+ ;;
25
+ s )
26
+ SERVICE="${OPTARG}"
27
+ ;;
28
+ \? )
29
+ log "🛑" "Unknown option: ${opt}"
30
+ usage "${description}" "${usage_args}" "${usage_pre}"
31
+ ;;
32
+ : )
33
+ log "🛑" "Invalid option: ${opt} requires an argument"
34
+ usage "${description}" "${usage_args}" "${usage_pre}"
35
+ ;;
36
+ esac
37
+ done
38
+ shift $((OPTIND -1))
39
+
40
+ if [ $# -eq 0 ]; then
41
+ log "🛑" "You must provide a command e.g. bash or ls -l"
42
+ usage "${description}" "${usage_args}" "${usage_pre}"
43
+ fi
44
+
45
+
46
+ exec_hook_if_exists "exec.pre"
47
+
48
+ log "🚂" "Running '${*}' inside container with service name '${SERVICE}'"
49
+
50
+ docker \
51
+ compose \
52
+ --file docker-compose.dx.yaml \
53
+ --project-name "${PROJECT_NAME}" \
54
+ --env-file "${ENV_FILE}" \
55
+ exec \
56
+ "${SERVICE}" \
57
+ "${@}"
58
+
59
+ # vim: ft=bash
data/dx/prune ADDED
@@ -0,0 +1,19 @@
1
+ #!/bin/bash
2
+
3
+ set -e
4
+
5
+ SCRIPT_DIR=$( cd -- "$( dirname -- "${0}" )" > /dev/null 2>&1 && pwd )
6
+
7
+ . "${SCRIPT_DIR}/dx.sh.lib"
8
+ require_command "docker"
9
+ load_docker_compose_env
10
+
11
+ usage_on_help "Prune containers for this repo" "" "" "" "${@}"
12
+
13
+ for container_id in $(docker container ls -a -f "name=^${PROJECT_NAME}-.*-1$" --format="{{.ID}}"); do
14
+ log "🗑" "Removing container with id '${container_id}'"
15
+ docker container rm "${container_id}"
16
+ done
17
+ echo "🧼" "Containers removed"
18
+
19
+ # vim: ft=bash
@@ -0,0 +1,144 @@
1
+ # shellcheck shell=bash
2
+
3
+ fatal() {
4
+ remainder=${*:2}
5
+ if [ -z "$remainder" ]; then
6
+ log "🛑" "${@}"
7
+ else
8
+ log "${@}"
9
+ fi
10
+ exit 1
11
+ }
12
+
13
+ log() {
14
+ emoji=$1
15
+ remainder=${*:2}
16
+ if [ -z "${NO_EMOJI}" ]; then
17
+ echo "[ ${0} ] ${*}"
18
+ else
19
+ # if remainder is empty that means no emoji was passed
20
+ if [ -z "$remainder" ]; then
21
+ echo "[ ${0} ] ${*}"
22
+ else # emoji was passed, but we ignore it
23
+ echo "[ ${0} ] ${remainder}"
24
+ fi
25
+ fi
26
+ }
27
+
28
+ debug() {
29
+ message=$1
30
+ if [ -z "${DOCKBOX_DEBUG}" ]; then
31
+ return
32
+ fi
33
+ log "🐛" "${message}"
34
+ }
35
+
36
+ usage() {
37
+ description=$1
38
+ arg_names=$2
39
+ pre_hook=$3
40
+ post_hook=$4
41
+ echo "usage: ${0} [-h] ${arg_names}"
42
+ if [ -n "${description}" ]; then
43
+ echo
44
+ echo "DESCRIPTION"
45
+ echo " ${description}"
46
+ fi
47
+ if [ -n "${pre_hook}" ] || [ -n "${post_hook}" ]; then
48
+ echo
49
+ echo "HOOKS"
50
+ if [ -n "${pre_hook}" ]; then
51
+ echo " ${pre_hook} - if present, called before the main action"
52
+ fi
53
+ if [ -n "${post_hook}" ]; then
54
+ echo " ${post_hook} - if present, called after the main action"
55
+ fi
56
+ fi
57
+ exit 0
58
+ }
59
+
60
+ usage_on_help() {
61
+ description=$1
62
+ arg_names=$2
63
+ pre_hook=$3
64
+ post_hook=$4
65
+ # These are the args passed to the invocation so this
66
+ # function can determine if the user requested help
67
+ cli_args=( "${@:5}" )
68
+
69
+ for arg in "${cli_args[@]}"; do
70
+ if [ "${arg}" = "-h" ] || [ "${arg}" = "--help" ]; then
71
+ usage "${description}" "${arg_names}" "${pre_hook}" "${post_hook}"
72
+ fi
73
+ done
74
+ }
75
+
76
+ # Read user input into the variable 'INPUT'
77
+ #
78
+ # Args:
79
+ #
80
+ # [1] - an emoji to use for messages
81
+ # [2] - the message explaining what input is being requested
82
+ # [3] - a default value to use if no value is provided
83
+ #
84
+ # Respects NO_EMOJI when outputing messages to the user
85
+ user_input() {
86
+ emoji=$1
87
+ message=$2
88
+ default=$3
89
+ prompt=$4
90
+
91
+ if [ -z "$message" ]; then
92
+ echo "user_input requires a message"
93
+ exit 1
94
+ fi
95
+
96
+ INPUT=
97
+
98
+ if [ -z "${prompt}" ]; then
99
+ prompt=$(log "${emoji}" "Value: ")
100
+ if [ -n "${default}" ]; then
101
+ prompt=$(log "${emoji}" "Value (or hit return to use '${default}'): ")
102
+ fi
103
+ fi
104
+
105
+ while [ -z "${INPUT}" ]; do
106
+
107
+ log "$emoji" "$message"
108
+ read -r -p "${prompt}" INPUT
109
+ if [ -z "$INPUT" ]; then
110
+ INPUT=$default
111
+ fi
112
+ if [ -z "$INPUT" ]; then
113
+ log "😶", "You must provide a value"
114
+ fi
115
+ done
116
+ }
117
+
118
+ user_confirm() {
119
+ user_input "$1" "$2" "$3" "y/n> "
120
+ }
121
+
122
+ require_not_exist() {
123
+ file=$1
124
+ message=$2
125
+ if [ -e "${file}" ]; then
126
+ fatal "$message"
127
+ fi
128
+ }
129
+ require_exist() {
130
+ file=$1
131
+ message=$2
132
+ if [ ! -e "${file}" ]; then
133
+ fatal "$message"
134
+ fi
135
+ }
136
+
137
+ require_command() {
138
+ command_name=$1
139
+ if ! command -v "${command_name}" >/dev/null 2>&1; then
140
+ fatal "Command '${command_name}' not found - it is required for this script to run"
141
+ fi
142
+ }
143
+
144
+ # vim: ft=bash
@@ -0,0 +1,38 @@
1
+ #!/bin/bash
2
+
3
+ set -e
4
+
5
+ # Ideally, the message below is shown after everything starts up. We can't
6
+ # achieve this using healtchecks because the interval for a healtcheck is
7
+ # also an initial delay, and we don't really want to do healthchecks on
8
+ # our DB or Redis every 2 seconds. So, we sleep just a bit to let
9
+ # the other containers start up and vomit out their output first.
10
+ sleep 2
11
+ # Output some helpful messaging when invoking `dx/start` (which itself is
12
+ # a convenience script for `docker compose up`.
13
+ #
14
+ # Adding this to work around the mild inconvenience of the `app` container's
15
+ # entrypoint generating no output.
16
+ #
17
+ cat <<-'PROMPT'
18
+
19
+
20
+
21
+ 🎉 Dev Environment Initialized! 🎉
22
+
23
+ ℹ️ To use this environment, open a new terminal and run
24
+
25
+ dx/exec bash
26
+
27
+ 🕹 Use `ctrl-c` to exit.
28
+
29
+
30
+
31
+ PROMPT
32
+
33
+ # Using `sleep infinity` instead of `tail -f /dev/null`. This may be a
34
+ # performance improvement based on the conversation on a semi-related
35
+ # StackOverflow page.
36
+ #
37
+ # @see https://stackoverflow.com/a/41655546
38
+ sleep infinity
data/dx/start ADDED
@@ -0,0 +1,30 @@
1
+ #!/bin/bash
2
+
3
+ set -e
4
+
5
+ SCRIPT_DIR=$( cd -- "$( dirname -- "${0}" )" > /dev/null 2>&1 && pwd )
6
+
7
+ . "${SCRIPT_DIR}/dx.sh.lib"
8
+ require_command "docker"
9
+ load_docker_compose_env
10
+
11
+ usage_on_help "Starts all services, including a container in which to run your app" "" "" "" "${@}"
12
+
13
+ log "🚀" "Starting docker-compose.dx.yml"
14
+
15
+ BUILD=--build
16
+ if [ "${1}" == "--no-build" ]; then
17
+ BUILD=
18
+ fi
19
+
20
+ docker \
21
+ compose \
22
+ --file docker-compose.dx.yml \
23
+ --project-name "${PROJECT_NAME}" \
24
+ --env-file "${ENV_FILE}" \
25
+ up \
26
+ "${BUILD}" \
27
+ --timestamps \
28
+ --force-recreate
29
+
30
+ # vim: ft=bash
data/dx/stop ADDED
@@ -0,0 +1,23 @@
1
+ #!/bin/bash
2
+
3
+ set -e
4
+
5
+ SCRIPT_DIR=$( cd -- "$( dirname -- "${0}" )" > /dev/null 2>&1 && pwd )
6
+
7
+ . "${SCRIPT_DIR}/dx.sh.lib"
8
+ require_command "docker"
9
+ load_docker_compose_env
10
+
11
+ usage_on_help "Stops all services, the container in which to run your app and removes any volumes" "" "" "" "${@}"
12
+
13
+ log "🚀" "Stopping docker-compose.dx.yml"
14
+
15
+ docker \
16
+ compose \
17
+ --file docker-compose.dx.yml \
18
+ --project-name "${PROJECT_NAME}" \
19
+ --env-file "${ENV_FILE}" \
20
+ down \
21
+ --volumes
22
+
23
+ # vim: ft=bash
@@ -1,6 +1,6 @@
1
1
  module WithClues
2
2
  class BrowserLogs
3
- def dump(notifier, page:, context:)
3
+ def dump(notifier, page:, context:, captured_logs: [])
4
4
  if !page.respond_to?(:driver)
5
5
  notifier.notify "Something may be wrong. page (#{page.class}) does not respond to #driver"
6
6
  return
@@ -1,18 +1,31 @@
1
1
  module WithClues
2
2
  class Html
3
- def dump(notifier, page:, context:)
3
+ def dump(notifier, page:, context:, captured_logs: [])
4
+ access_page_html = if page.respond_to?(:html)
5
+ ->(page) { page.html }
6
+ elsif page.respond_to?(:content)
7
+ ->(page) { page.content }
8
+ elsif page.respond_to?(:native)
9
+ ->(page) { page.native }
10
+ else
11
+ notifier.notify "Something may be wrong. page (#{page.class}) does not respond to #html, #native, or #content"
12
+ return
13
+ end
4
14
  notifier.blank_line
5
15
  notifier.notify "HTML {"
6
16
  notifier.blank_line
7
- if page.respond_to?(:html)
8
- notifier.notify_raw page.html
9
- elsif page.respond_to?(:native)
10
- notifier.notify_raw page.native
11
- else
12
- notifier.notify "[!] Something may be wrong. page (#{page.class}) does not respond to #html or #native"
13
- end
17
+ notifier.notify_raw access_page_html.(page)
14
18
  notifier.blank_line
15
19
  notifier.notify "} END HTML"
20
+ if captured_logs.any?
21
+ notifier.notify "LOGS {"
22
+ notifier.blank_line
23
+ captured_logs.each do |log|
24
+ notifier.notify_raw log
25
+ end
26
+ notifier.blank_line
27
+ notifier.notify "} END LOGS"
28
+ end
16
29
  end
17
30
  end
18
31
  end
@@ -17,6 +17,15 @@ module WithClues
17
17
  # unexpectedly failing
18
18
  def with_clues(context=nil, &block)
19
19
  notifier = WithClues::Notifier.new($stdout)
20
+ captured_logs = []
21
+ if defined?(page) && page.respond_to?(:on)
22
+ begin
23
+ page.on("console", ->(msg) { captured_logs << msg.text })
24
+ rescue => ex
25
+ raise ex
26
+ notifier.notify "'page' was defined and responds to #on, however invoking it generated an exception: #{ex}"
27
+ end
28
+ end
20
29
  block.()
21
30
  notifier.notify "A passing test has been wrapped with `with_clues`. You should remove the call to `with_clues`"
22
31
  rescue Exception => ex
@@ -27,7 +36,7 @@ module WithClues
27
36
  if defined?(page)
28
37
  notifier.notify "Test failed: #{ex.message}"
29
38
  @@clue_classes[:require_page].each do |klass|
30
- klass.new.dump(notifier, context: context, page: page)
39
+ klass.new.dump(notifier, context: context, page: page, captured_logs: captured_logs)
31
40
  end
32
41
  end
33
42
  raise ex
@@ -14,15 +14,17 @@ module WithClues
14
14
 
15
15
  return BadParams.new(two_arg_method.errors)
16
16
 
17
- elsif params.size == 3
18
- three_arg_method = ThreeArgMethod.new(params)
17
+ elsif params.size == 4
18
+ three_arg_method = FourArgMethod.new(params)
19
19
  if three_arg_method.valid?
20
20
  return RequiresPageObject.new
21
21
  end
22
22
  return BadParams.new(three_arg_method.errors)
23
23
  end
24
24
 
25
- BadParams.new([])
25
+ BadParams.new([
26
+ "dump (#{unbound_method.owner}) accepted #{params.size} arguments, not 2 or 4. Got: #{params.map(&:to_s).join(", ")}, should be one positional and either one keyword arg named 'context:' or three keyword args named 'context:', 'page:', and 'captured_logs:'"
27
+ ])
26
28
  end
27
29
 
28
30
  def standard_implementation?
@@ -62,6 +64,9 @@ module WithClues
62
64
  @name
63
65
  end
64
66
  end
67
+ def to_s
68
+ "#<#{self.class} #{name}/#{@type}"
69
+ end
65
70
  end
66
71
 
67
72
  class TwoArgMethod
@@ -84,7 +89,7 @@ module WithClues
84
89
  @errors << "Param #{param_number}, #{param.name}, is not a required keyword param"
85
90
  end
86
91
  if !param.named?(*allowed_names)
87
- @errors << "Param #{param_number}, #{param.name}, should be named context:"
92
+ @errors << "Param #{param_number}, #{param.name}, should be one of #{allowed_names.join(',')}"
88
93
  end
89
94
  end
90
95
 
@@ -93,14 +98,15 @@ module WithClues
93
98
  end
94
99
  end
95
100
 
96
- class ThreeArgMethod < TwoArgMethod
101
+ class FourArgMethod < TwoArgMethod
97
102
  def initialize(params)
98
103
  super(params)
99
104
  require_keyword(3,params[2])
105
+ require_keyword(4,params[3])
100
106
  end
101
107
  private
102
108
  def allowed_names
103
- [ :context, :page ]
109
+ [ :context, :page, :captured_logs ]
104
110
  end
105
111
  end
106
112
 
@@ -126,7 +132,11 @@ module WithClues
126
132
 
127
133
  class BadParams < CustomClueMethodAnalysis
128
134
  def initialize(errors)
129
- @message = errors.empty? ? DEFAULT_ERROR : errors.join(", ")
135
+ if errors.empty?
136
+ raise ArgumentError,"BadParams requires errors"
137
+ else
138
+ @message = errors.map(&:to_s).join(", ")
139
+ end
130
140
  end
131
141
 
132
142
  DEFAULT_ERROR = "dump must take one required param, one keyword param named context: and an optional keyword param named page:"
@@ -1,3 +1,3 @@
1
1
  module WithClues
2
- VERSION="1.2.0"
2
+ VERSION="1.3.0"
3
3
  end
data/with_clues.gemspec CHANGED
@@ -1,13 +1,11 @@
1
1
  require_relative "lib/with_clues/version"
2
2
 
3
- #require_relative "lib/«gem»/version"
4
-
5
3
  Gem::Specification.new do |spec|
6
4
  spec.name = "with_clues"
7
5
  spec.version = WithClues::VERSION
8
6
  spec.authors = ["Dave Copeland"]
9
7
  spec.email = ["davec@naildrivin5.com"]
10
- spec.summary = %q{WTF does this do?}
8
+ spec.summary = %q{Temporarily add context to failing tests to get more information, such as what HTML was being examined when a browser-based test fails.}
11
9
  spec.homepage = "https://sustainable-rails.com"
12
10
  spec.license = "Hippocratic"
13
11
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: with_clues
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dave Copeland
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-12-09 00:00:00.000000000 Z
11
+ date: 2024-10-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -73,24 +73,33 @@ executables: []
73
73
  extensions: []
74
74
  extra_rdoc_files: []
75
75
  files:
76
- - ".circleci/config.yml"
77
76
  - ".gitignore"
78
77
  - ".rspec"
79
78
  - ".tool-versions"
80
79
  - CHANGELOG.md
81
80
  - CODE_OF_CONDUCT.md
82
81
  - CONTRIBUTING.md
82
+ - Dockerfile.dx
83
83
  - Gemfile
84
84
  - LICENSE.md
85
85
  - README.md
86
86
  - Rakefile
87
87
  - bin/ci
88
88
  - bin/console
89
- - bin/mk_circle_config
90
- - bin/mk_gem
89
+ - bin/matrix
91
90
  - bin/rake
92
91
  - bin/rspec
93
92
  - bin/setup
93
+ - docker-compose.dx.yml
94
+ - dx/build
95
+ - dx/docker-compose.env
96
+ - dx/dx.sh.lib
97
+ - dx/exec
98
+ - dx/prune
99
+ - dx/setupkit.sh.lib
100
+ - dx/show-help-in-app-container-then-wait.sh
101
+ - dx/start
102
+ - dx/stop
94
103
  - lib/with_clues.rb
95
104
  - lib/with_clues/browser_logs.rb
96
105
  - lib/with_clues/html.rb
@@ -121,8 +130,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
121
130
  - !ruby/object:Gem::Version
122
131
  version: '0'
123
132
  requirements: []
124
- rubygems_version: 3.3.3
133
+ rubygems_version: 3.5.21
125
134
  signing_key:
126
135
  specification_version: 4
127
- summary: WTF does this do?
136
+ summary: Temporarily add context to failing tests to get more information, such as
137
+ what HTML was being examined when a browser-based test fails.
128
138
  test_files: []
data/.circleci/config.yml DELETED
@@ -1,98 +0,0 @@
1
- # THIS IS GENERATED - DO NOT EDIT
2
- # regenerate with bin/mk_circle_config
3
- # You are very welcome
4
- ---
5
- version: '2.1'
6
- jobs:
7
- ruby__2_6:
8
- docker:
9
- - image: cimg/ruby:2.6
10
- steps:
11
- - checkout
12
- - run:
13
- name: Setup for build
14
- command: bin/setup
15
- - run:
16
- name: Ensure bin/setup is idempotent
17
- command: bin/setup
18
- - run:
19
- name: Create the test results dir
20
- command: mkdir -p /tmp/test-results/2.6
21
- - run:
22
- name: Run all tests
23
- command: bin/ci /tmp/test-results/2.6/rspec_results.xml
24
- - store_test_results:
25
- path: "/tmp/test-results/2.6"
26
- - store_artifacts:
27
- path: "/tmp/test-results/2.6"
28
- ruby__2_7:
29
- docker:
30
- - image: cimg/ruby:2.7
31
- steps:
32
- - checkout
33
- - run:
34
- name: Setup for build
35
- command: bin/setup
36
- - run:
37
- name: Ensure bin/setup is idempotent
38
- command: bin/setup
39
- - run:
40
- name: Create the test results dir
41
- command: mkdir -p /tmp/test-results/2.7
42
- - run:
43
- name: Run all tests
44
- command: bin/ci /tmp/test-results/2.7/rspec_results.xml
45
- - store_test_results:
46
- path: "/tmp/test-results/2.7"
47
- - store_artifacts:
48
- path: "/tmp/test-results/2.7"
49
- ruby__3_0:
50
- docker:
51
- - image: cimg/ruby:3.0
52
- steps:
53
- - checkout
54
- - run:
55
- name: Setup for build
56
- command: bin/setup
57
- - run:
58
- name: Ensure bin/setup is idempotent
59
- command: bin/setup
60
- - run:
61
- name: Create the test results dir
62
- command: mkdir -p /tmp/test-results/3.0
63
- - run:
64
- name: Run all tests
65
- command: bin/ci /tmp/test-results/3.0/rspec_results.xml
66
- - store_test_results:
67
- path: "/tmp/test-results/3.0"
68
- - store_artifacts:
69
- path: "/tmp/test-results/3.0"
70
- ruby__3_1:
71
- docker:
72
- - image: cimg/ruby:3.1
73
- steps:
74
- - checkout
75
- - run:
76
- name: Setup for build
77
- command: bin/setup
78
- - run:
79
- name: Ensure bin/setup is idempotent
80
- command: bin/setup
81
- - run:
82
- name: Create the test results dir
83
- command: mkdir -p /tmp/test-results/3.1
84
- - run:
85
- name: Run all tests
86
- command: bin/ci /tmp/test-results/3.1/rspec_results.xml
87
- - store_test_results:
88
- path: "/tmp/test-results/3.1"
89
- - store_artifacts:
90
- path: "/tmp/test-results/3.1"
91
- workflows:
92
- version: 2
93
- all_rubies:
94
- jobs:
95
- - ruby__2_6
96
- - ruby__2_7
97
- - ruby__3_0
98
- - ruby__3_1
data/bin/mk_circle_config DELETED
@@ -1,84 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require "yaml"
4
- require "pathname"
5
-
6
- circle_config = {
7
- "version" => "2.1",
8
- "jobs" => {},
9
- "workflows" => {
10
- "version" => 2,
11
- "all_rubies" => {
12
- "jobs" => [
13
- ],
14
- },
15
- }
16
- }
17
-
18
- supported_rubies = [
19
- "2.6",
20
- "2.7",
21
- "3.0",
22
- "3.1",
23
- ]
24
-
25
- supported_rubies.each do |ruby_verison|
26
-
27
- test_results_dir = "/tmp/test-results/#{ruby_verison}"
28
- job_name = "ruby__#{ruby_verison.gsub(/\./,"_")}"
29
-
30
- job = {
31
- "docker" => [
32
- {
33
- "image" => "cimg/ruby:#{ruby_verison}",
34
- }
35
- ],
36
- "steps" => [
37
- "checkout",
38
- {
39
- "run" => {
40
- "name" => "Setup for build",
41
- "command" => "bin/setup",
42
- }
43
- },
44
- {
45
- "run" => {
46
- "name" => "Ensure bin/setup is idempotent",
47
- "command" => "bin/setup",
48
- }
49
- },
50
- {
51
- "run" => {
52
- "name" => "Create the test results dir",
53
- "command" => "mkdir -p #{test_results_dir}",
54
- }
55
- },
56
- {
57
- "run" => {
58
- "name" => "Run all tests",
59
- "command" => "bin/ci #{test_results_dir}/rspec_results.xml",
60
- }
61
- },
62
- {
63
- "store_test_results" => {
64
- "path" => test_results_dir,
65
- }
66
- },
67
- {
68
- "store_artifacts" => {
69
- "path" => test_results_dir,
70
- }
71
- },
72
- ]
73
- }
74
- circle_config["jobs"][job_name] = job
75
- circle_config["workflows"]["all_rubies"]["jobs"] << job_name
76
- end
77
-
78
- circle_config_file = (Pathname(__FILE__).dirname / ".." / ".circleci" / "config.yml").expand_path
79
- File.open(circle_config_file,"w") do |file|
80
- file.puts "# THIS IS GENERATED - DO NOT EDIT"
81
- file.puts "# regenerate with bin/mk_circle_config"
82
- file.puts "# You are very welcome"
83
- file.puts circle_config.to_yaml
84
- end
data/bin/mk_gem DELETED
@@ -1,73 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require "fileutils"
4
- require "pathname"
5
-
6
- include FileUtils
7
-
8
- gem_name = ARGV[0]
9
-
10
- if gem_name.nil?
11
- puts "usage: #{$0} gem_name"
12
- exit 1
13
- end
14
-
15
- root = Pathname(__FILE__).dirname / ".."
16
-
17
- module_name = gem_name.split(/_/).map { |part|
18
- part.capitalize
19
- }.join
20
-
21
- mkdir_p root / "lib"
22
-
23
- File.open(root / "lib" / "#{gem_name}.rb", "w") do |file|
24
- file.puts "module #{module_name}"
25
- file.puts "end"
26
- end
27
-
28
- mkdir_p root / "lib" / gem_name
29
-
30
- File.open(root / "lib" / gem_name / "version.rb", "w") do |file|
31
- file.puts "module #{module_name}"
32
- file.puts " VERSION=\"1.0.0\""
33
- file.puts "end"
34
- end
35
-
36
- gemspec = File.read("rubygem.gemspec")
37
- File.open(root / "#{gem_name}.gemspec","w") do |file|
38
- file.puts "require_relative \"lib/#{gem_name}/version\""
39
- file.puts
40
- gemspec.split(/\n/).each do |line|
41
- if line =~ /^\s*spec.name/
42
- file.puts " spec.name = \"#{gem_name}\""
43
- elsif line =~ /^\s*spec.version/
44
- file.puts " spec.version = #{module_name}::VERSION"
45
- elsif line.include?("«gem_name»")
46
- file.puts line.gsub(/«gem_name»/,gem_name)
47
- else
48
- file.puts line
49
- end
50
- end
51
- end
52
-
53
- license = File.read(root / "LICENSE.md")
54
- File.open(root / "LICENSE.md","w") do |file|
55
- license.split(/\n/).each_with_index do |line,index|
56
- if index == 0
57
- file.puts "[#{gem_name}] Copyright (2021) (David Copeland)(“Licensor”)"
58
- else
59
- file.puts line
60
- end
61
- end
62
- end
63
-
64
- readme = File.read(root / "README.md")
65
- File.open(root / "README.md","w") do |file|
66
- license.split(/\n/).each_with_index do |line,index|
67
- if index == 0
68
- file.puts "# #{gem_name} - does a thing"
69
- else
70
- file.puts line
71
- end
72
- end
73
- end