From 5ecbcc07660fc6a929a3efa36a2549eb064ed36d Mon Sep 17 00:00:00 2001 From: chejennifer <69875368+chejennifer@users.noreply.github.com> Date: Thu, 5 Oct 2023 10:16:52 -0700 Subject: [PATCH] [vis tools] Add webdriver tests (#3656) - add webdriver tests for the new vis tools - also use selected source information when get BQ SQL queries --- server/webdriver/tests/map_test.py | 2 +- server/webdriver/tests/vis_map_test.py | 253 ++++++++++++++++ server/webdriver/tests/vis_scatter_test.py | 270 ++++++++++++++++++ server/webdriver/tests/vis_timeline_test.py | 256 +++++++++++++++++ static/js/apps/visualization/app_context.tsx | 3 + .../apps/visualization/selected_options.tsx | 6 +- .../vis_type_configs/map_config.tsx | 17 +- .../vis_type_configs/timeline_config.tsx | 14 +- static/js/shared/facet_selector.tsx | 14 +- 9 files changed, 823 insertions(+), 12 deletions(-) create mode 100644 server/webdriver/tests/vis_map_test.py create mode 100644 server/webdriver/tests/vis_scatter_test.py create mode 100644 server/webdriver/tests/vis_timeline_test.py diff --git a/server/webdriver/tests/map_test.py b/server/webdriver/tests/map_test.py index 5c38131046..1df8ce51b4 100644 --- a/server/webdriver/tests/map_test.py +++ b/server/webdriver/tests/map_test.py @@ -52,7 +52,7 @@ def test_server_and_page(self): def test_charts_from_url(self): """Given the url directly, test the page shows up correctly""" - # Load Scatter Tool page with Statistical Variables. + # Load Map Tool page with Statistical Variables. self.driver.get(self.url_ + MAP_URL + URL_HASH_1) # Wait until the chart has loaded. diff --git a/server/webdriver/tests/vis_map_test.py b/server/webdriver/tests/vis_map_test.py new file mode 100644 index 0000000000..088ae2e7fa --- /dev/null +++ b/server/webdriver/tests/vis_map_test.py @@ -0,0 +1,253 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import urllib +import urllib.request + +from selenium.webdriver.common.by import By +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.support.ui import WebDriverWait + +from server.webdriver.base import WebdriverBaseTest +import server.webdriver.shared as shared + +MAP_URL = '/tools/visualization#visType=map' +URL_HASH_1 = '&place=geoId/06&placeType=County&sv=%7B"dcid"%3A"Count_Person_Female"%7D' +PLACE_SEARCH_CA = 'California' + + +# Class to test the map visualization tool. +class TestVisMap(WebdriverBaseTest): + + def test_server_and_page(self): + """Test the server can run successfully.""" + self.driver.get(self.url_ + MAP_URL) + + # Assert 200 HTTP code: successful page load. + req = urllib.request.Request(self.driver.current_url) + with urllib.request.urlopen(req) as response: + self.assertEqual(response.getcode(), 200) + + # Assert 200 HTTP code: successful JS generation. + req = urllib.request.Request(self.url_ + '/visualization.js') + with urllib.request.urlopen(req) as response: + self.assertEqual(response.getcode(), 200) + + # Assert page title is correct. + TITLE_TEXT = "Tools - Data Commons" + WebDriverWait(self.driver, + self.TIMEOUT_SEC).until(EC.title_contains(TITLE_TEXT)) + self.assertEqual(TITLE_TEXT, self.driver.title) + + # Wait until the page has loaded. + element_present = EC.presence_of_element_located( + (By.CLASS_NAME, 'visualization-app')) + WebDriverWait(self.driver, self.TIMEOUT_SEC).until(element_present) + + # Assert page heading and selected tab are correct. + page_header = self.driver.find_element(By.CSS_SELECTOR, '.info-content h3') + self.assertEqual(page_header.text, 'Map Explorer') + selected_tab = self.driver.find_element( + By.CSS_SELECTOR, ".vis-type-selector .selected .label") + self.assertEqual(selected_tab.text, 'Map Explorer') + + def test_charts_from_url(self): + """Given the url directly, test the page shows up correctly""" + self.driver.get(self.url_ + MAP_URL + URL_HASH_1) + + # Wait until the chart has loaded. + WebDriverWait(self.driver, self.TIMEOUT_SEC).until(shared.charts_rendered) + + # Assert place name is correct. + place_name_chip = self.driver.find_element( + By.CSS_SELECTOR, '.selected-option-chip.place .chip-content') + self.assertTrue('California' in place_name_chip.text) + + # Assert place type is correct. + place_type_chip = self.driver.find_element( + By.CSS_SELECTOR, '.selected-option-chip.place-type .chip-content') + self.assertTrue('County' in place_type_chip.text) + + # Assert stat var is correct. + stat_var_chip = self.driver.find_element( + By.CSS_SELECTOR, '.selected-option-chip.stat-var .chip-content') + self.assertTrue('Female Population' in stat_var_chip.text) + + # Assert chart is correct. + chart_title = self.driver.find_element(By.CSS_SELECTOR, + '.map-chart .chart-headers h4') + self.assertEqual(chart_title.text, "Female Population (2021)") + chart_map = self.driver.find_element(By.ID, 'map-items') + map_regions = chart_map.find_elements(By.TAG_NAME, 'path') + self.assertEqual(len(map_regions), 58) + + # Assert rankings are correct. + ranking_titles = self.driver.find_elements(By.CSS_SELECTOR, + '.ranking-header-section h4') + self.assertEqual(len(ranking_titles), 2) + self.assertEqual(ranking_titles[0].text, 'Top Places') + self.assertEqual(ranking_titles[1].text, 'Bottom Places') + ranking_items = self.driver.find_elements(By.CSS_SELECTOR, + '.ranking-list .place-name') + self.assertEqual(len(ranking_items), 10) + self.assertEqual(ranking_items[0].text, 'Los Angeles County, CA') + self.assertEqual(ranking_items[9].text, 'Trinity County, CA') + + # Click per capita and assert results are correct. + per_capita_checkbox = self.driver.find_element( + By.CSS_SELECTOR, + '.chart-footer-options .chart-option .form-check-input') + per_capita_checkbox.click() + shared.wait_for_loading(self.driver) + element_present = EC.presence_of_element_located((By.ID, 'map-items')) + WebDriverWait(self.driver, self.TIMEOUT_SEC).until(element_present) + chart_map = self.driver.find_element(By.ID, 'map-items') + map_regions = chart_map.find_elements(By.TAG_NAME, 'path') + self.assertEqual(len(map_regions), 58) + ranking_items = self.driver.find_elements(By.CSS_SELECTOR, + '.ranking-list .place-name') + self.assertEqual(len(ranking_items), 10) + self.assertEqual(ranking_items[0].text, 'Butte County, CA') + self.assertEqual(ranking_items[9].text, 'Del Norte County, CA') + + # Edit source and assert results are correct. + edit_source_button = self.driver.find_element( + By.CLASS_NAME, 'source-selector-open-modal-button') + edit_source_button.click() + element_present = EC.presence_of_element_located( + (By.CLASS_NAME, 'modal-body')) + WebDriverWait(self.driver, self.TIMEOUT_SEC).until(element_present) + source_options = self.driver.find_elements(By.CSS_SELECTOR, + '.source-selector-option input') + self.assertEqual(len(source_options), 4) + source_options[3].click() + update_button = self.driver.find_element(By.CSS_SELECTOR, + '.modal-footer .btn') + update_button.click() + shared.wait_for_loading(self.driver) + chart_title = self.driver.find_element(By.CSS_SELECTOR, + '.map-chart .chart-headers h4') + self.assertEqual(chart_title.text, "Female Population (2019)") + chart_map = self.driver.find_element(By.ID, 'map-items') + map_regions = chart_map.find_elements(By.TAG_NAME, 'path') + self.assertEqual(len(map_regions), 58) + ranking_items = self.driver.find_elements(By.CSS_SELECTOR, + '.ranking-list .place-name') + self.assertEqual(len(ranking_items), 10) + self.assertEqual(ranking_items[0].text, 'Nevada County, CA') + self.assertEqual(ranking_items[9].text, 'San Francisco County, CA') + + def test_manually_enter_options(self): + """Test entering place and stat var options manually will cause chart to + show up. + """ + self.driver.get(self.url_ + MAP_URL) + + # Click the start button + element_present = EC.presence_of_element_located( + (By.CLASS_NAME, 'start-button')) + WebDriverWait(self.driver, self.TIMEOUT_SEC).until(element_present) + self.driver.find_element(By.CLASS_NAME, 'start-button').click() + + # Type california into the search box. + element_present = EC.presence_of_element_located((By.ID, 'location-field')) + WebDriverWait(self.driver, self.TIMEOUT_SEC).until(element_present) + search_box_input = self.driver.find_element(By.ID, 'ac') + search_box_input.send_keys(PLACE_SEARCH_CA) + + # Click on the first result. + element_present = EC.presence_of_element_located( + (By.CLASS_NAME, 'pac-item')) + WebDriverWait(self.driver, self.TIMEOUT_SEC).until(element_present) + first_result = self.driver.find_element(By.CSS_SELECTOR, + '.pac-item:nth-child(1)') + first_result.click() + + # Click continue + element_present = EC.presence_of_element_located( + (By.CLASS_NAME, 'continue-button')) + WebDriverWait(self.driver, self.TIMEOUT_SEC).until(element_present) + self.driver.find_element(By.CLASS_NAME, 'continue-button').click() + + # Wait for place types to load and click on 'County' + element_present = EC.presence_of_element_located( + (By.CSS_SELECTOR, '.place-type-selector .form-check-input')) + WebDriverWait(self.driver, self.TIMEOUT_SEC).until(element_present) + place_type_inputs = self.driver.find_elements(By.CSS_SELECTOR, + '.place-type-selector label') + for input in place_type_inputs: + if input.text == 'County': + input.click() + break + + # Click continue + element_present = EC.presence_of_element_located( + (By.CLASS_NAME, 'continue-button')) + WebDriverWait(self.driver, self.TIMEOUT_SEC).until(element_present) + self.driver.find_element(By.CLASS_NAME, 'continue-button').click() + + # Choose stat var + shared.wait_for_loading(self.driver) + shared.click_sv_group(self.driver, "Demographics") + element_present = EC.presence_of_element_located( + (By.ID, 'Median_Age_Persondc/g/Demographics-Median_Age_Person')) + WebDriverWait(self.driver, self.TIMEOUT_SEC).until(element_present) + self.driver.find_element( + By.ID, 'Median_Age_Persondc/g/Demographics-Median_Age_Person').click() + + # Click continue + element_present = EC.presence_of_element_located( + (By.CLASS_NAME, 'continue-button')) + WebDriverWait(self.driver, self.TIMEOUT_SEC).until(element_present) + self.driver.find_element(By.CLASS_NAME, 'continue-button').click() + + # Assert chart is correct. + shared.wait_for_loading(self.driver) + chart_title = self.driver.find_element(By.CSS_SELECTOR, + '.map-chart .chart-headers h4') + self.assertEqual(chart_title.text, "Median Age of Population (2021)") + chart_map = self.driver.find_element(By.ID, 'map-items') + map_regions = chart_map.find_elements(By.TAG_NAME, 'path') + self.assertEqual(len(map_regions), 58) + + # Assert rankings are correct. + ranking_titles = self.driver.find_elements(By.CSS_SELECTOR, + '.ranking-header-section h4') + self.assertEqual(len(ranking_titles), 2) + self.assertEqual(ranking_titles[0].text, 'Top Places') + self.assertEqual(ranking_titles[1].text, 'Bottom Places') + ranking_items = self.driver.find_elements(By.CSS_SELECTOR, + '.ranking-list .place-name') + self.assertEqual(len(ranking_items), 10) + self.assertEqual(ranking_items[0].text, 'Trinity County, CA') + self.assertEqual(ranking_items[9].text, 'Kings County, CA') + + def test_landing_page_link(self): + """Test one of the links on the landing page + """ + self.driver.get(self.url_ + MAP_URL) + + # Click a link on the landing page + element_present = EC.presence_of_element_located( + (By.CLASS_NAME, 'info-content')) + WebDriverWait(self.driver, self.TIMEOUT_SEC).until(element_present) + self.driver.find_element(By.CSS_SELECTOR, '.info-content a').click() + + # Assert chart loads + shared.wait_for_loading(self.driver) + element_present = EC.presence_of_element_located((By.ID, 'map-items')) + WebDriverWait(self.driver, self.TIMEOUT_SEC).until(element_present) + chart_map = self.driver.find_element(By.ID, 'map-items') + map_regions = chart_map.find_elements(By.TAG_NAME, 'path') + self.assertGreater(len(map_regions), 1) diff --git a/server/webdriver/tests/vis_scatter_test.py b/server/webdriver/tests/vis_scatter_test.py new file mode 100644 index 0000000000..7758e3faa0 --- /dev/null +++ b/server/webdriver/tests/vis_scatter_test.py @@ -0,0 +1,270 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import urllib +import urllib.request + +from selenium.webdriver.common.by import By +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.support.ui import WebDriverWait + +from server.webdriver.base import WebdriverBaseTest +import server.webdriver.shared as shared + +SCATTER_URL = '/tools/visualization#visType=scatter' +URL_HASH_1 = '&place=geoId/06&placeType=County&sv=%7B"dcid"%3A"Count_Person_NoHealthInsurance"%7D___%7B"dcid"%3A"Count_Person_Female"%7D' +PLACE_SEARCH_CA = 'California' + + +# Class to test the scatter visualization tool. +class TestVisScatter(WebdriverBaseTest): + + def test_server_and_page(self): + """Test the server can run successfully.""" + self.driver.get(self.url_ + SCATTER_URL) + + # Assert 200 HTTP code: successful page load. + req = urllib.request.Request(self.driver.current_url) + with urllib.request.urlopen(req) as response: + self.assertEqual(response.getcode(), 200) + + # Assert 200 HTTP code: successful JS generation. + req = urllib.request.Request(self.url_ + '/visualization.js') + with urllib.request.urlopen(req) as response: + self.assertEqual(response.getcode(), 200) + + # Assert page title is correct. + TITLE_TEXT = "Tools - Data Commons" + WebDriverWait(self.driver, + self.TIMEOUT_SEC).until(EC.title_contains(TITLE_TEXT)) + self.assertEqual(TITLE_TEXT, self.driver.title) + + # Wait until the page has loaded. + element_present = EC.presence_of_element_located( + (By.CLASS_NAME, 'visualization-app')) + WebDriverWait(self.driver, self.TIMEOUT_SEC).until(element_present) + + # Assert page heading and selected tab are correct. + page_header = self.driver.find_element(By.CSS_SELECTOR, '.info-content h3') + self.assertEqual(page_header.text, 'Scatter Plot') + selected_tab = self.driver.find_element( + By.CSS_SELECTOR, ".vis-type-selector .selected .label") + self.assertEqual(selected_tab.text, 'Scatter Plot') + + def test_charts_from_url(self): + """Given the url directly, test the page shows up correctly""" + self.driver.get(self.url_ + SCATTER_URL + URL_HASH_1) + + # Wait until the chart has loaded. + WebDriverWait(self.driver, self.TIMEOUT_SEC).until(shared.charts_rendered) + + # Assert place name is correct. + place_name_chip = self.driver.find_element( + By.CSS_SELECTOR, '.selected-option-chip.place .chip-content') + self.assertTrue('California' in place_name_chip.text) + + # Assert place type is correct. + place_type_chip = self.driver.find_element( + By.CSS_SELECTOR, '.selected-option-chip.place-type .chip-content') + self.assertTrue('County' in place_type_chip.text) + + # Assert stat var is correct. + stat_var_chips = self.driver.find_elements( + By.CSS_SELECTOR, '.selected-option-chip.stat-var .chip-content') + self.assertTrue( + 'Population Without Health Insurance' in stat_var_chips[0].text) + self.assertTrue('Female Population' in stat_var_chips[1].text) + + # Assert chart is correct. + chart_title = self.driver.find_element(By.CSS_SELECTOR, + '.scatter-chart .chart-headers h4') + self.assertEqual( + chart_title.text, + "Population Without Health Insurance (2021) vs Female Population (2021)" + ) + chart = self.driver.find_element(By.ID, 'scatterplot') + circles = chart.find_elements(By.TAG_NAME, 'circle') + self.assertGreater(len(circles), 20) + + # Click all the chart options and assert results are correct. + chart_option_inputs = self.driver.find_elements( + By.CSS_SELECTOR, + '.chart-footer-options .chart-option .form-check-input') + for input in chart_option_inputs: + input.click() + shared.wait_for_loading(self.driver) + y_axis_label = self.driver.find_element(By.CSS_SELECTOR, + '#scatterplot .y-axis-label') + self.assertTrue( + 'Population Without Health Insurance (%)' in y_axis_label.text) + x_axis_label = self.driver.find_element(By.CSS_SELECTOR, + '#scatterplot .x-axis-label') + self.assertTrue('Female Population (%)' in x_axis_label.text) + chart = self.driver.find_element(By.ID, 'scatterplot') + circles = chart.find_elements(By.TAG_NAME, 'circle') + self.assertGreater(len(circles), 20) + quadrant_lines = chart.find_elements(By.CSS_SELECTOR, + '#scatterplot .quadrant-line') + self.assertEqual(len(quadrant_lines), 2) + dot_labels = chart.find_element(By.CLASS_NAME, 'dot-label').find_elements( + By.TAG_NAME, 'text') + self.assertEqual(len(circles), len(dot_labels)) + + # Edit source and assert results are correct. + edit_source_button = self.driver.find_element( + By.CLASS_NAME, 'source-selector-open-modal-button') + edit_source_button.click() + element_present = EC.presence_of_element_located( + (By.CLASS_NAME, 'modal-body')) + WebDriverWait(self.driver, self.TIMEOUT_SEC).until(element_present) + source_option_sections = self.driver.find_elements( + By.CLASS_NAME, 'source-selector-options-section') + self.assertEqual(len(source_option_sections), 2) + # Open the selection section for each sv + self.driver.find_elements(By.CLASS_NAME, + 'source-selector-trigger')[0].click() + self.driver.find_elements(By.CLASS_NAME, + 'source-selector-trigger')[1].click() + # Update the source for the Count_Person_Female sv + element_present = EC.element_to_be_clickable( + (By.NAME, 'Count_Person_Female')) + WebDriverWait(self.driver, self.TIMEOUT_SEC).until(element_present) + source_options = self.driver.find_elements(By.NAME, 'Count_Person_Female') + self.assertGreater(len(source_options), 3) + source_options[3].click() + update_button = self.driver.find_element(By.CSS_SELECTOR, + '.modal-footer .btn') + update_button.click() + shared.wait_for_loading(self.driver) + # Check that results are correct + chart_title = self.driver.find_element(By.CSS_SELECTOR, + '.scatter-chart .chart-headers h4') + self.assertEqual( + chart_title.text, + "Population Without Health Insurance (2021) vs Female Population (2019)" + ) + chart = self.driver.find_element(By.ID, 'scatterplot') + circles = chart.find_elements(By.TAG_NAME, 'circle') + self.assertGreater(len(circles), 20) + + def test_manually_enter_options(self): + """Test entering place and stat var options manually will cause chart to + show up. + """ + self.driver.get(self.url_ + SCATTER_URL.replace('#visType=scatter', '')) + + # Click the scatter tab + vis_type_options = self.driver.find_elements(By.CLASS_NAME, + 'vis-type-option') + for vis_type in vis_type_options: + if 'Scatter' in vis_type.text: + vis_type.click() + break + page_header = self.driver.find_element(By.CSS_SELECTOR, '.info-content h3') + self.assertEqual(page_header.text, 'Scatter Plot') + + # Click the start button + element_present = EC.presence_of_element_located( + (By.CLASS_NAME, 'start-button')) + WebDriverWait(self.driver, self.TIMEOUT_SEC).until(element_present) + self.driver.find_element(By.CLASS_NAME, 'start-button').click() + + # Type california into the search box. + element_present = EC.presence_of_element_located((By.ID, 'location-field')) + WebDriverWait(self.driver, self.TIMEOUT_SEC).until(element_present) + search_box_input = self.driver.find_element(By.ID, 'ac') + search_box_input.send_keys(PLACE_SEARCH_CA) + + # Click on the first result. + element_present = EC.presence_of_element_located( + (By.CLASS_NAME, 'pac-item')) + WebDriverWait(self.driver, self.TIMEOUT_SEC).until(element_present) + first_result = self.driver.find_element(By.CSS_SELECTOR, + '.pac-item:nth-child(1)') + first_result.click() + + # Click continue + element_present = EC.presence_of_element_located( + (By.CLASS_NAME, 'continue-button')) + WebDriverWait(self.driver, self.TIMEOUT_SEC).until(element_present) + self.driver.find_element(By.CLASS_NAME, 'continue-button').click() + + # Wait for place types to load and click on 'County' + element_present = EC.presence_of_element_located( + (By.CSS_SELECTOR, '.place-type-selector .form-check-input')) + WebDriverWait(self.driver, self.TIMEOUT_SEC).until(element_present) + place_type_inputs = self.driver.find_elements(By.CSS_SELECTOR, + '.place-type-selector label') + for input in place_type_inputs: + if input.text == 'County': + input.click() + break + + # Click continue + element_present = EC.presence_of_element_located( + (By.CLASS_NAME, 'continue-button')) + WebDriverWait(self.driver, self.TIMEOUT_SEC).until(element_present) + self.driver.find_element(By.CLASS_NAME, 'continue-button').click() + + # Choose stat vars + shared.wait_for_loading(self.driver) + shared.click_sv_group(self.driver, "Demographics") + element_present = EC.presence_of_element_located( + (By.ID, 'Median_Age_Persondc/g/Demographics-Median_Age_Person')) + WebDriverWait(self.driver, self.TIMEOUT_SEC).until(element_present) + self.driver.find_element( + By.ID, 'Median_Age_Persondc/g/Demographics-Median_Age_Person').click() + shared.wait_for_loading(self.driver) + element_present = EC.presence_of_element_located( + (By.ID, 'Median_Income_Persondc/g/Demographics-Median_Income_Person')) + WebDriverWait(self.driver, self.TIMEOUT_SEC).until(element_present) + self.driver.find_element( + By.ID, + 'Median_Income_Persondc/g/Demographics-Median_Income_Person').click() + + # Click continue + element_present = EC.presence_of_element_located( + (By.CLASS_NAME, 'continue-button')) + WebDriverWait(self.driver, self.TIMEOUT_SEC).until(element_present) + self.driver.find_element(By.CLASS_NAME, 'continue-button').click() + + # Assert chart is correct + WebDriverWait(self.driver, self.TIMEOUT_SEC).until(shared.charts_rendered) + chart_title = self.driver.find_element(By.CSS_SELECTOR, + '.scatter-chart .chart-headers h4') + self.assertEqual( + chart_title.text, + 'Median Age of Population (2021) vs Median Income of a Population (2021)' + ) + chart = self.driver.find_element(By.ID, 'scatterplot') + circles = chart.find_elements(By.TAG_NAME, 'circle') + self.assertGreater(len(circles), 20) + + def test_landing_page_link(self): + """Test one of the links on the landing page + """ + self.driver.get(self.url_ + SCATTER_URL) + + # Click a link on the landing page + element_present = EC.presence_of_element_located( + (By.CLASS_NAME, 'info-content')) + WebDriverWait(self.driver, self.TIMEOUT_SEC).until(element_present) + self.driver.find_element(By.CSS_SELECTOR, '.info-content a').click() + + # Assert chart loads + element_present = EC.presence_of_element_located((By.ID, 'scatterplot')) + WebDriverWait(self.driver, self.TIMEOUT_SEC).until(element_present) + chart = self.driver.find_element(By.ID, 'scatterplot') + circles = chart.find_elements(By.TAG_NAME, 'circle') + self.assertGreater(len(circles), 20) diff --git a/server/webdriver/tests/vis_timeline_test.py b/server/webdriver/tests/vis_timeline_test.py new file mode 100644 index 0000000000..dc254b881c --- /dev/null +++ b/server/webdriver/tests/vis_timeline_test.py @@ -0,0 +1,256 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import urllib +import urllib.request + +from selenium.webdriver.common.by import By +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.support.ui import WebDriverWait + +from server.webdriver.base import WebdriverBaseTest +import server.webdriver.shared as shared + +TIMELINE_URL = '/tools/visualization#visType=timeline' +URL_HASH_1 = '&place=geoId/06___geoId/08&sv=%7B"dcid"%3A"Median_Age_Person"%7D___%7B"dcid"%3A"Count_Person_Female"%7D___%7B"dcid"%3A"Count_Person_Male"%7D' +PLACE_SEARCH_CA = 'California' +PLACE_SEARCH_USA = 'USA' + + +# Class to test the timeline visualization tool. +class TestVisTimeline(WebdriverBaseTest): + + def test_server_and_page(self): + """Test the server can run successfully.""" + self.driver.get(self.url_ + TIMELINE_URL) + + # Assert 200 HTTP code: successful page load. + req = urllib.request.Request(self.driver.current_url) + with urllib.request.urlopen(req) as response: + self.assertEqual(response.getcode(), 200) + + # Assert 200 HTTP code: successful JS generation. + req = urllib.request.Request(self.url_ + '/visualization.js') + with urllib.request.urlopen(req) as response: + self.assertEqual(response.getcode(), 200) + + # Assert page title is correct. + TITLE_TEXT = "Tools - Data Commons" + WebDriverWait(self.driver, + self.TIMEOUT_SEC).until(EC.title_contains(TITLE_TEXT)) + self.assertEqual(TITLE_TEXT, self.driver.title) + + # Wait until the page has loaded. + element_present = EC.presence_of_element_located( + (By.CLASS_NAME, 'visualization-app')) + WebDriverWait(self.driver, self.TIMEOUT_SEC).until(element_present) + + # Assert page heading and selected tab are correct. + page_header = self.driver.find_element(By.CSS_SELECTOR, '.info-content h3') + self.assertEqual(page_header.text, 'Timeline') + selected_tab = self.driver.find_element( + By.CSS_SELECTOR, ".vis-type-selector .selected .label") + self.assertEqual(selected_tab.text, 'Timeline') + + def test_charts_from_url(self): + """Given the url directly, test the page shows up correctly""" + self.driver.get(self.url_ + TIMELINE_URL + URL_HASH_1) + + # Wait until the chart has loaded + WebDriverWait(self.driver, self.TIMEOUT_SEC).until(shared.charts_rendered) + + # Assert place name is correct + place_name_chips = self.driver.find_elements( + By.CSS_SELECTOR, '.selected-option-chip.place .chip-content') + self.assertEqual(len(place_name_chips), 2) + self.assertTrue('California' in place_name_chips[0].text) + self.assertTrue('Colorado' in place_name_chips[1].text) + + # Assert stat var is correct + stat_var_chips = self.driver.find_elements( + By.CSS_SELECTOR, '.selected-option-chip.stat-var .chip-content') + self.assertEqual(len(stat_var_chips), 3) + self.assertTrue('Median Age of Population' in stat_var_chips[0].text) + self.assertTrue('Female Population' in stat_var_chips[1].text) + self.assertTrue('Male Population' in stat_var_chips[2].text) + + # Assert charts are correct + charts = self.driver.find_elements(By.CSS_SELECTOR, '.chart.timeline') + self.assertEqual(len(charts), 2) + chart_lines = charts[0].find_elements(By.CLASS_NAME, 'line') + self.assertEqual(len(chart_lines), 4) + chart_lines = charts[1].find_elements(By.CLASS_NAME, 'line') + self.assertEqual(len(chart_lines), 2) + + # Click per capita and assert results are correct + per_capita_checkbox = self.driver.find_element( + By.CSS_SELECTOR, + '.chart-footer-options .chart-option .form-check-input') + per_capita_checkbox.click() + shared.wait_for_loading(self.driver) + element_present = EC.presence_of_element_located( + (By.CSS_SELECTOR, '.chart.timeline')) + WebDriverWait(self.driver, self.TIMEOUT_SEC).until(element_present) + charts = self.driver.find_elements(By.CSS_SELECTOR, '.chart.timeline') + self.assertEqual(len(charts), 2) + chart_lines = charts[0].find_elements(By.CLASS_NAME, 'line') + self.assertEqual(len(chart_lines), 4) + + # Edit source and assert results are correct + edit_source_button = self.driver.find_element( + By.CLASS_NAME, 'source-selector-open-modal-button') + edit_source_button.click() + element_present = EC.presence_of_element_located( + (By.CLASS_NAME, 'modal-body')) + WebDriverWait(self.driver, self.TIMEOUT_SEC).until(element_present) + source_option_sections = self.driver.find_elements( + By.CLASS_NAME, 'source-selector-options-section') + self.assertEqual(len(source_option_sections), 2) + # Open the selection section for each sv + self.driver.find_elements(By.CLASS_NAME, + 'source-selector-trigger')[0].click() + self.driver.find_elements(By.CLASS_NAME, + 'source-selector-trigger')[1].click() + # Update the source for the Count_Person_Female sv + element_present = EC.element_to_be_clickable( + (By.NAME, 'Count_Person_Female')) + WebDriverWait(self.driver, self.TIMEOUT_SEC).until(element_present) + source_options = self.driver.find_elements(By.NAME, 'Count_Person_Female') + for option in source_options: + parent = option.find_element(By.XPATH, '..') + if 'OECDRegionalStatistics' in parent.text: + option.click() + update_button = self.driver.find_element(By.CSS_SELECTOR, + '.modal-footer .btn') + update_button.click() + shared.wait_for_loading(self.driver) + chart_sources = self.driver.find_element(By.CLASS_NAME, 'sources') + self.assertTrue('stats.oecd.org' in chart_sources.text) + charts = self.driver.find_elements(By.CSS_SELECTOR, '.chart.timeline') + self.assertEqual(len(charts), 2) + chart_lines = charts[0].find_elements(By.CLASS_NAME, 'line') + self.assertEqual(len(chart_lines), 4) + chart_lines = charts[1].find_elements(By.CLASS_NAME, 'line') + self.assertEqual(len(chart_lines), 2) + + def test_manually_enter_options(self): + """Test entering place and stat var options manually will cause chart to + show up. + """ + self.driver.get(self.url_ + TIMELINE_URL.replace('#visType=timeline', '')) + + # Click the timeline tab + vis_type_options = self.driver.find_elements(By.CLASS_NAME, + 'vis-type-option') + for vis_type in vis_type_options: + if 'Timeline' in vis_type.text: + vis_type.click() + break + page_header = self.driver.find_element(By.CSS_SELECTOR, '.info-content h3') + self.assertEqual(page_header.text, 'Timeline') + + # Click the start button + element_present = EC.presence_of_element_located( + (By.CLASS_NAME, 'start-button')) + WebDriverWait(self.driver, self.TIMEOUT_SEC).until(element_present) + self.driver.find_element(By.CLASS_NAME, 'start-button').click() + + # Type california into the search box. + element_present = EC.presence_of_element_located((By.ID, 'location-field')) + WebDriverWait(self.driver, self.TIMEOUT_SEC).until(element_present) + search_box_input = self.driver.find_element(By.ID, 'ac') + search_box_input.send_keys(PLACE_SEARCH_CA) + + # Click on the first result. + element_present = EC.presence_of_element_located( + (By.CLASS_NAME, 'pac-item')) + WebDriverWait(self.driver, self.TIMEOUT_SEC).until(element_present) + first_result = self.driver.find_element(By.CSS_SELECTOR, + '.pac-item:nth-child(1)') + first_result.click() + + # Type USA into the search box after California has been selected. + element_present = EC.presence_of_element_located( + (By.CSS_SELECTOR, '.place-selector-selections .selected-place')) + WebDriverWait(self.driver, self.TIMEOUT_SEC).until(element_present) + search_box_input = self.driver.find_element(By.ID, 'ac') + search_box_input.send_keys(PLACE_SEARCH_USA) + + # Click on the first result. + element_present = EC.presence_of_element_located( + (By.CLASS_NAME, 'pac-item')) + WebDriverWait(self.driver, self.TIMEOUT_SEC).until(element_present) + first_result = self.driver.find_element(By.CSS_SELECTOR, + '.pac-item:nth-child(1)') + first_result.click() + + # Click continue after USA has been selected. + element_present = EC.text_to_be_present_in_element( + (By.CLASS_NAME, 'place-selector-selections'), + 'United States of America') + WebDriverWait(self.driver, self.TIMEOUT_SEC).until(element_present) + element_present = EC.presence_of_element_located( + (By.CLASS_NAME, 'continue-button')) + WebDriverWait(self.driver, self.TIMEOUT_SEC).until(element_present) + self.driver.find_element(By.CLASS_NAME, 'continue-button').click() + + # Choose stat vars + shared.wait_for_loading(self.driver) + shared.click_sv_group(self.driver, "Demographics") + element_present = EC.presence_of_element_located( + (By.ID, 'Median_Age_Persondc/g/Demographics-Median_Age_Person')) + WebDriverWait(self.driver, self.TIMEOUT_SEC).until(element_present) + self.driver.find_element( + By.ID, 'Median_Age_Persondc/g/Demographics-Median_Age_Person').click() + shared.wait_for_loading(self.driver) + element_present = EC.presence_of_element_located( + (By.ID, 'Median_Income_Persondc/g/Demographics-Median_Income_Person')) + WebDriverWait(self.driver, self.TIMEOUT_SEC).until(element_present) + self.driver.find_element( + By.ID, + 'Median_Income_Persondc/g/Demographics-Median_Income_Person').click() + + # Click continue after selection is done loading. + shared.wait_for_loading(self.driver) + element_present = EC.presence_of_element_located( + (By.CLASS_NAME, 'continue-button')) + WebDriverWait(self.driver, self.TIMEOUT_SEC).until(element_present) + self.driver.find_element(By.CLASS_NAME, 'continue-button').click() + + # Assert chart is correct + WebDriverWait(self.driver, self.TIMEOUT_SEC).until(shared.charts_rendered) + charts = self.driver.find_elements(By.CSS_SELECTOR, '.chart.timeline') + self.assertEqual(len(charts), 2) + chart_lines = charts[0].find_elements(By.CLASS_NAME, 'line') + self.assertEqual(len(chart_lines), 2) + chart_lines = charts[1].find_elements(By.CLASS_NAME, 'line') + self.assertEqual(len(chart_lines), 2) + + def test_landing_page_link(self): + """Test one of the links on the landing page + """ + self.driver.get(self.url_ + TIMELINE_URL) + + # Click a link on the landing page + element_present = EC.presence_of_element_located( + (By.CLASS_NAME, 'info-content')) + WebDriverWait(self.driver, self.TIMEOUT_SEC).until(element_present) + self.driver.find_element(By.CSS_SELECTOR, '.info-content a').click() + + # Assert chart loads + WebDriverWait(self.driver, self.TIMEOUT_SEC).until(shared.charts_rendered) + charts = self.driver.find_elements(By.CSS_SELECTOR, '.chart.timeline') + self.assertEqual(len(charts), 1) + chart_lines = charts[0].find_elements(By.CLASS_NAME, 'line') + self.assertEqual(len(chart_lines), 3) diff --git a/static/js/apps/visualization/app_context.tsx b/static/js/apps/visualization/app_context.tsx index 4368edd2ad..0a8be65391 100644 --- a/static/js/apps/visualization/app_context.tsx +++ b/static/js/apps/visualization/app_context.tsx @@ -29,6 +29,7 @@ import { URL_PARAMS, } from "../../constants/app/visualization_constants"; import { GA_EVENT_PAGE_VIEW, triggerGAEvent } from "../../shared/ga_events"; +import { StatMetadata } from "../../shared/stat_types"; import { getStatVarInfo, StatVarInfo } from "../../shared/stat_var"; import { NamedNode, NamedTypedPlace } from "../../shared/types"; import { @@ -57,6 +58,8 @@ export interface ContextStatVar { date?: string; denom?: string; facetId?: string; + // This is the stat metadata for the selected facetId + facetInfo?: StatMetadata; } export interface AppContextType { diff --git a/static/js/apps/visualization/selected_options.tsx b/static/js/apps/visualization/selected_options.tsx index d34f376ab0..4e91980f06 100644 --- a/static/js/apps/visualization/selected_options.tsx +++ b/static/js/apps/visualization/selected_options.tsx @@ -82,7 +82,7 @@ export function SelectedOptions(): JSX.Element { {places.map((place) => { return (
@@ -179,7 +179,7 @@ export function SelectedOptions(): JSX.Element { > by
-
+
straighten {enclosedPlaceType} @@ -218,7 +218,7 @@ export function SelectedOptions(): JSX.Element { {statVars.map((sv) => { return (
diff --git a/static/js/apps/visualization/vis_type_configs/map_config.tsx b/static/js/apps/visualization/vis_type_configs/map_config.tsx index 4bbf2fdfaf..7db131170a 100644 --- a/static/js/apps/visualization/vis_type_configs/map_config.tsx +++ b/static/js/apps/visualization/vis_type_configs/map_config.tsx @@ -26,6 +26,7 @@ import { MapTile } from "../../../components/tiles/map_tile"; import { RankingTile } from "../../../components/tiles/ranking_tile"; import { FacetSelector } from "../../../shared/facet_selector"; import { GA_VALUE_TOOL_CHART_OPTION_PER_CAPITA } from "../../../shared/ga_events"; +import { StatMetadata } from "../../../shared/stat_types"; import { StatVarHierarchyType } from "../../../shared/types"; import { getNonPcQuery, getPcQuery } from "../../../tools/map/bq_query_utils"; import { getAllChildPlaceTypes } from "../../../tools/map/util"; @@ -57,12 +58,20 @@ function getFacetSelector(appContext: AppContextType): JSX.Element { }, ]; }); - const onSvFacetIdUpdated = (svFacetId: Record) => { - if (svFacetId[statVar.dcid] === statVar.facetId) { + const onSvFacetIdUpdated = ( + svFacetId: Record, + metadataMap: Record + ) => { + if ( + svFacetId[statVar.dcid] === statVar.facetId || + _.isEmpty(appContext.statVars) + ) { return; } const newStatVars = _.cloneDeep(appContext.statVars); + const facetId = svFacetId[newStatVars[0].dcid]; newStatVars[0].facetId = svFacetId[newStatVars[0].dcid]; + newStatVars[0].facetInfo = metadataMap[facetId]; appContext.setStatVars(newStatVars); }; return ( @@ -188,7 +197,7 @@ function getSqlQueryFn(appContext: AppContextType): () => string { appContext.places[0].dcid, appContext.enclosedPlaceType, contextStatVar.date, - {} + contextStatVar.facetInfo || {} ); } else { return getNonPcQuery( @@ -196,7 +205,7 @@ function getSqlQueryFn(appContext: AppContextType): () => string { appContext.places[0].dcid, appContext.enclosedPlaceType, contextStatVar.date, - {} + contextStatVar.facetInfo || {} ); } }; diff --git a/static/js/apps/visualization/vis_type_configs/timeline_config.tsx b/static/js/apps/visualization/vis_type_configs/timeline_config.tsx index 7aa866c311..cd27e4aedc 100644 --- a/static/js/apps/visualization/vis_type_configs/timeline_config.tsx +++ b/static/js/apps/visualization/vis_type_configs/timeline_config.tsx @@ -250,12 +250,22 @@ function getSqlQueryFn(appContext: AppContextType): () => string { perCapita: !!sampleSvSpec.denom, }; } + // map of stat var dcid to the facet id. + const metahashMap = {}; + // map of stat var dcid to map of facet id to stat metadata. + const metadataMap = {}; + appContext.statVars.forEach((sv) => { + metahashMap[sv.dcid] = sv.facetId || ""; + if (sv.facetId) { + metadataMap[sv.dcid] = { [sv.facetId]: sv.facetInfo || {} }; + } + }); return () => { return getTimelineSqlQuery( { chartOrder, chartIdToOptions, chartIdToStatVars: groups }, appContext.places.map((place) => place.dcid), - {}, - {} + metahashMap, + metadataMap ); }; } diff --git a/static/js/shared/facet_selector.tsx b/static/js/shared/facet_selector.tsx index 44664574fa..baa42658a0 100644 --- a/static/js/shared/facet_selector.tsx +++ b/static/js/shared/facet_selector.tsx @@ -65,7 +65,10 @@ interface FacetSelectorPropType { // Promise that returns the available facet for each stat var facetListPromise: Promise; // Callback function that is run when new facets are selected - onSvFacetIdUpdated: (svFacetId: Record) => void; + onSvFacetIdUpdated: ( + svFacetId: Record, + metadataMap: Record + ) => void; } export function FacetSelector(props: FacetSelectorPropType): JSX.Element { @@ -164,7 +167,14 @@ export function FacetSelector(props: FacetSelectorPropType): JSX.Element { ); function onConfirm(): void { - props.onSvFacetIdUpdated(modalSelections); + const metadataMap = {}; + facetList.forEach((facetInfo: FacetSelectorFacetInfo) => { + const selectedFacetId = modalSelections[facetInfo.dcid]; + if (selectedFacetId) { + metadataMap[selectedFacetId] = facetInfo.metadataMap[selectedFacetId]; + } + }); + props.onSvFacetIdUpdated(modalSelections, metadataMap); setModalOpen(false); } }