rmm commited on
Commit
fc76ddb
·
1 Parent(s): a9c8ccb

test: visual test of presented content, persistent to tab switching

Browse files

- seleniumbase runs the testing, clicking/uploading etc, and then
looking for the image elements that are generated once the ML
inference is complete
- added a property to one button so selenium can locate it

Files changed (3) hide show
  1. .gitignore +1 -0
  2. src/main.py +2 -1
  3. tests/test_visual_main.py +244 -0
.gitignore CHANGED
@@ -142,6 +142,7 @@ venv.bak/
142
 
143
  # mkdocs documentation
144
  /site
 
145
 
146
  # mypy
147
  .mypy_cache/
 
142
 
143
  # mkdocs documentation
144
  /site
145
+ docs/site
146
 
147
  # mypy
148
  .mypy_cache/
src/main.py CHANGED
@@ -226,7 +226,8 @@ def main() -> None:
226
 
227
  if st.session_state.workflow_fsm.is_in_state('data_entry_validated'):
228
  # show the button, enabled. If pressed, we start the ML model (And advance state)
229
- if tab_inference.button("Identify with cetacean classifier"):
 
230
  cetacean_classifier = AutoModelForImageClassification.from_pretrained(
231
  "Saving-Willy/cetacean-classifier",
232
  revision=classifier_revision,
 
226
 
227
  if st.session_state.workflow_fsm.is_in_state('data_entry_validated'):
228
  # show the button, enabled. If pressed, we start the ML model (And advance state)
229
+ if tab_inference.button("Identify with cetacean classifier",
230
+ key="button_infer_ceteans"):
231
  cetacean_classifier = AutoModelForImageClassification.from_pretrained(
232
  "Saving-Willy/cetacean-classifier",
233
  revision=classifier_revision,
tests/test_visual_main.py ADDED
@@ -0,0 +1,244 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pathlib import Path
2
+ import time
3
+ import pytest
4
+ from seleniumbase import BaseCase
5
+ from selenium.webdriver.common.by import By
6
+ from selenium.webdriver.support.ui import WebDriverWait
7
+ from selenium.webdriver.support import expected_conditions as EC
8
+
9
+ BaseCase.main(__name__, __file__)
10
+
11
+ # Set the paths to the images and csv file
12
+ repo_path = Path(__file__).resolve().parents[1]
13
+ imgpath = repo_path / "tests/data/rand_images"
14
+ img_f1 = imgpath / "img_001.jpg"
15
+ img_f2 = imgpath / "img_002.jpg"
16
+ img_f3 = imgpath / "img_003.jpg"
17
+ #csvpath = repo_path / "tests/data/test_csvs"
18
+ #csv_f1 = csvpath / "debian.csv"
19
+
20
+ mk_visible = """
21
+ var input = document.querySelector('[data-testid="stFileUploaderDropzoneInput"]');
22
+ input.style.display = 'block';
23
+ input.style.opacity = '1';
24
+ input.style.visibility = 'visible';
25
+ """
26
+
27
+ def wait_for_element(self, by, selector, timeout=10):
28
+ # example usage:
29
+ # element = self.wait_for_element(By.XPATH, "//p[contains(text(), 'Species for observation')]")
30
+
31
+ return WebDriverWait(self.driver, timeout).until(
32
+ EC.presence_of_element_located((by, selector))
33
+ )
34
+
35
+
36
+ def find_all_button_paths(self):
37
+ buttons = self.find_elements("button")
38
+ for button in buttons:
39
+ print(f"\nButton found:")
40
+ print(f"Text: {button.text.strip()}")
41
+ print(f"HTML: {button.get_attribute('outerHTML')}")
42
+ print("-" * 50)
43
+
44
+ def check_columns_and_images(self, exp_cols:int, exp_imgs:int=4):
45
+ # Find all columns
46
+ columns = self.find_elements("div[class*='stColumn']")
47
+
48
+ # Check number of columns
49
+ assert len(columns) == exp_cols, f"Expected exp_cols columns but found {len(columns)}"
50
+
51
+ # Check images in each column
52
+ for i, column in enumerate(columns, 1):
53
+ # Find all images within this column's image containers
54
+ images = self.find_elements(
55
+ f"div[class*='stColumn']:nth-child({i}) div[data-testid='stImageContainer'] img"
56
+ )
57
+
58
+ # Check number of images in this column
59
+ assert len(images) == exp_imgs, f"Column {i} has {len(images)} images instead of {exp_imgs}"
60
+
61
+
62
+ def analyze_species_columns_debug(self):
63
+ # First, just try to find any divs
64
+ all_divs = self.find_elements(By.TAG_NAME, "div")
65
+ print(f"Found {len(all_divs)} total divs")
66
+
67
+ # Then try to find stColumn divs
68
+ column_divs = self.find_elements(By.XPATH, "//div[contains(@class, 'stColumn')]")
69
+ print(f"Found {len(column_divs)} column divs")
70
+
71
+ # Try to find any elements containing our text, without class restrictions
72
+ text_elements = self.find_elements(
73
+ By.XPATH, "//*[contains(text(), 'Species for observation')]"
74
+ )
75
+ print(f"Found {len(text_elements)} elements with 'Species for observation' text")
76
+
77
+ # If we found text elements, print their tag names and class names to help debug
78
+ for elem in text_elements:
79
+ print(f"Tag: {elem.tag_name}, Class: {elem.get_attribute('class')}")
80
+
81
+ def analyze_species_columns(self, exp_cols:int, exp_imgs:int=4, exp_visible:bool=True):
82
+ # Find all columns that contain the specific text pattern
83
+ cur_tab = get_selected_tab(self)
84
+ print(f"Current tab: {cur_tab['text']} ({cur_tab['id']})" )
85
+
86
+ #"div[class*='stColumn']//div[contains(text(), 'Species for observation')]"
87
+ spec_labels = self.find_elements(
88
+ By.XPATH,
89
+ "//p[contains(text(), 'Species for observation')]"
90
+ )
91
+
92
+ # This gets us the text containers, need to go back up to the column
93
+ species_columns = [lbl.find_element(By.XPATH, "./ancestor::div[contains(@class, 'stColumn')]")
94
+ for lbl in spec_labels]
95
+
96
+ print(f" Found {len(species_columns)} species columns (total {len(spec_labels)} species labels)")
97
+ assert len(species_columns) == exp_cols, f"Expected {exp_cols} columns but found {len(species_columns)}"
98
+
99
+
100
+ for i, column in enumerate(species_columns, 1):
101
+ # Get the species number text
102
+ species_text = column.find_element(
103
+ #By.XPATH, ".//div[contains(text(), 'Species for observation')]"
104
+ By.XPATH, ".//p[contains(text(), 'Species for observation')]"
105
+ )
106
+ print(f" Analyzing col {i}:{species_text.text} {species_text.get_attribute('outerHTML')} | ")
107
+
108
+ # Find images in this specific column
109
+ images = column.find_elements(
110
+ By.XPATH, ".//div[@data-testid='stImageContainer']//img"
111
+ )
112
+ print(f" - Contains {len(images)} images (expected: {exp_imgs})")
113
+ assert len(images) == exp_imgs, f"Column {i} has {len(images)} images instead of {exp_imgs}"
114
+
115
+ # now let's refine the search to find the images that are actually displayed
116
+ visible_images = [img for img in column.find_elements(
117
+ By.XPATH, ".//div[@data-testid='stImageContainer']//img"
118
+ ) if img.is_displayed()]
119
+ print(f" - Contains {len(visible_images)} visible images")
120
+ if exp_visible:
121
+ assert len(visible_images) == exp_imgs, f"Column {i} has {len(visible_images)} visible images instead of {exp_imgs}"
122
+ else:
123
+ assert len(visible_images) == 0, f"Column {i} has {len(visible_images)} visible images instead of 0"
124
+
125
+
126
+ # even more strict test for visibility
127
+ # for img in images:
128
+ # style = img.get_attribute('style')
129
+ # computed_style = self.driver.execute_script(
130
+ # "return window.getComputedStyle(arguments[0])", img
131
+ # )
132
+ # print(f"Style: {style}")
133
+ # print(f"Visibility: {computed_style['visibility']}")
134
+ # print(f"Opacity: {computed_style['opacity']}")
135
+
136
+ def get_selected_tab(self):
137
+ selected_tab = self.find_element(
138
+ By.XPATH, "//div[@data-testid='stTabs']//button[@aria-selected='true']"
139
+ )
140
+ # Get the tab text
141
+ tab_text = selected_tab.find_element(By.TAG_NAME, "p").text
142
+ # Get the tab index (might be useful)
143
+ tab_id = selected_tab.get_attribute("id") # Usually ends with "-tab-X" where X is the index
144
+ return {
145
+ "text": tab_text,
146
+ "id": tab_id,
147
+ "element": selected_tab
148
+ }
149
+
150
+ def switch_tab(self, tab_number):
151
+ # Click the tab
152
+ self.click(f"div[data-testid='stTabs'] button[id$='-tab-{tab_number}'] p")
153
+
154
+ # Verify the switch
155
+ selected_tab = get_selected_tab(self)
156
+ if selected_tab["id"].endswith(f"-tab-{tab_number}"):
157
+ print(f"Successfully switched to tab {tab_number}: {selected_tab['text']}")
158
+ else:
159
+ raise Exception(f"Failed to switch to tab {tab_number}, current tab is {selected_tab['text']}")
160
+
161
+ class RecorderTest(BaseCase):
162
+
163
+ @pytest.mark.visual_slow
164
+ def test_species_presentation(self):
165
+ # this test goes through several steps of the workflow, primarily to get to the point
166
+ # that species columns are displayed.
167
+ # - setup steps:
168
+ # - open the app
169
+ # - upload two images
170
+ # - validate the data entry
171
+ # - click the infer button, wait for ML
172
+ # - the real test steps:
173
+ # - check the species columns are displayed
174
+ # - switch to another tab, check the columns are not displayed
175
+ # - switch back to the first tab, check the columns are displayed again
176
+
177
+ self.open("http://localhost:8501/")
178
+ time.sleep(4) # even in demo mode, on full script this is needed
179
+ # (the folium maps cause the scripts to rerun, which means the wait_for_element finds it, but
180
+ # the reload is going on and this makes the upload files (send_keys) command fail)
181
+
182
+ # make the file_uploader block visible -- for some reason even though we can see it, selenium can't...
183
+ wait_for_element(self, By.CSS_SELECTOR, '[data-testid="stFileUploaderDropzoneInput"]')
184
+ self.execute_script(mk_visible)
185
+ # send a list of files
186
+ self.send_keys(
187
+ 'input[data-testid="stFileUploaderDropzoneInput"]',
188
+ "\n".join([str(img_f1), str(img_f2)]),
189
+ )
190
+
191
+ # advance to the next step, by clicking the validate button (wait for it first)
192
+ wait_for_element(self, By.XPATH, "//button//strong[contains(text(), 'Validate')]")
193
+ self.click('button strong:contains("Validate")')
194
+ # validate the progress via the text display
195
+ self.assert_exact_text("Progress: 2/5. Current: data_entry_validated.", 'div[data-testid="stMarkdownContainer"] p em')
196
+
197
+ # check the tab bar is there, and the titles are correct
198
+ expected_texts = [
199
+ "Cetecean classifier", "Hotdog classifier", "Map",
200
+ "Dev:coordinates", "Log", "Beautiful cetaceans"
201
+ ]
202
+ self.assert_element("div[data-testid='stTabs']")
203
+
204
+ for i, text in enumerate(expected_texts):
205
+ selector = f"div[data-testid='stTabs'] button[id$='-tab-{i}'] p"
206
+ print(f"{i=}, {text=}, {selector=}")
207
+ self.assert_text(text, selector)
208
+ break # just do one, this is slow while debuggin
209
+
210
+ # dbg: look for buttons, find out which props will isolate the right one.
211
+ # find_all_button_paths(self)
212
+
213
+ self.assert_element(".st-key-button_infer_ceteans button")
214
+ self.click(".st-key-button_infer_ceteans button")
215
+
216
+ # check the state has advanced
217
+ self.assert_exact_text("Progress: 3/5. Current: ml_classification_completed.",
218
+ 'div[data-testid="stMarkdownContainer"] p em')
219
+
220
+ # on the inference tab, check the columns and images are rendered correctly
221
+ # - normally it is selected by default, but we can switch to it to be sure
222
+ # - then we do the test for the right number of columns and images per col,
223
+ # which should be visible
224
+ switch_tab(self, 0)
225
+ analyze_species_columns(self, exp_cols=2, exp_imgs=4, exp_visible=True)
226
+
227
+ # now, we want to select another tab, check somethign is present?
228
+ # then go back, and re-check the columns and images are re-rendered.
229
+ switch_tab(self, 4)
230
+ assert get_selected_tab(self)["id"].endswith("-tab-4")
231
+
232
+ # now we click the refresh button
233
+ self.click('button[data-testid="stBaseButton-secondary"]')
234
+ # and then select the first tab again
235
+ switch_tab(self, 0)
236
+ assert get_selected_tab(self)["id"].endswith("-tab-0")
237
+ # and check the columns and images are re-rendered
238
+ analyze_species_columns(self, exp_cols=2, exp_imgs=4, exp_visible=True)
239
+
240
+ # now go to some other tab, and check the columns and images are not visible
241
+ switch_tab(self, 2)
242
+ assert get_selected_tab(self)["id"].endswith("-tab-2")
243
+ analyze_species_columns(self, exp_cols=2, exp_imgs=4, exp_visible=False)
244
+