psdi-data-conversion 0.0.37__py3-none-any.whl → 0.0.39__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. psdi_data_conversion/app.py +64 -14
  2. psdi_data_conversion/constants.py +6 -5
  3. psdi_data_conversion/converter.py +20 -13
  4. psdi_data_conversion/converters/base.py +75 -68
  5. psdi_data_conversion/converters/c2x.py +14 -0
  6. psdi_data_conversion/converters/openbabel.py +12 -11
  7. psdi_data_conversion/database.py +361 -115
  8. psdi_data_conversion/dist.py +2 -1
  9. psdi_data_conversion/file_io.py +1 -2
  10. psdi_data_conversion/log_utility.py +1 -1
  11. psdi_data_conversion/main.py +152 -70
  12. psdi_data_conversion/static/content/index-versions/psdi-common-footer.html +12 -8
  13. psdi_data_conversion/static/content/psdi-common-footer.html +12 -8
  14. psdi_data_conversion/static/data/data.json +617 -3
  15. psdi_data_conversion/static/javascript/convert.js +54 -6
  16. psdi_data_conversion/static/javascript/convert_common.js +16 -2
  17. psdi_data_conversion/static/javascript/data.js +36 -4
  18. psdi_data_conversion/static/javascript/format.js +22 -9
  19. psdi_data_conversion/static/styles/format.css +7 -0
  20. psdi_data_conversion/templates/index.htm +57 -48
  21. psdi_data_conversion/testing/constants.py +3 -0
  22. psdi_data_conversion/testing/conversion_callbacks.py +4 -3
  23. psdi_data_conversion/testing/conversion_test_specs.py +44 -20
  24. psdi_data_conversion/testing/gui.py +362 -294
  25. psdi_data_conversion/testing/utils.py +38 -19
  26. {psdi_data_conversion-0.0.37.dist-info → psdi_data_conversion-0.0.39.dist-info}/METADATA +88 -4
  27. {psdi_data_conversion-0.0.37.dist-info → psdi_data_conversion-0.0.39.dist-info}/RECORD +30 -30
  28. {psdi_data_conversion-0.0.37.dist-info → psdi_data_conversion-0.0.39.dist-info}/WHEEL +0 -0
  29. {psdi_data_conversion-0.0.37.dist-info → psdi_data_conversion-0.0.39.dist-info}/entry_points.txt +0 -0
  30. {psdi_data_conversion-0.0.37.dist-info → psdi_data_conversion-0.0.39.dist-info}/licenses/LICENSE +0 -0
@@ -4,12 +4,12 @@
4
4
  Utilities to aid in testing of the GUI
5
5
  """
6
6
 
7
-
8
7
  import os
9
8
  import shutil
9
+ import time
10
+ from dataclasses import dataclass
10
11
  from tempfile import TemporaryDirectory
11
12
 
12
- import time
13
13
  import pytest
14
14
  from selenium.common.exceptions import TimeoutException
15
15
  from selenium.webdriver.common.alert import Alert
@@ -24,7 +24,9 @@ from psdi_data_conversion.converters.base import (FileConverterAbortException, F
24
24
  FileConverterInputException)
25
25
  from psdi_data_conversion.converters.openbabel import (COORD_GEN_KEY, COORD_GEN_QUAL_KEY, DEFAULT_COORD_GEN_QUAL,
26
26
  L_ALLOWED_COORD_GEN_QUALS, L_ALLOWED_COORD_GENS)
27
+ from psdi_data_conversion.database import get_format_info
27
28
  from psdi_data_conversion.file_io import split_archive_ext
29
+ from psdi_data_conversion.testing.constants import DEFAULT_ORIGIN
28
30
  from psdi_data_conversion.testing.utils import (ConversionTestInfo, ConversionTestSpec, SingleConversionTestSpec,
29
31
  get_input_test_data_loc)
30
32
 
@@ -43,324 +45,390 @@ def wait_and_find_element(root: WebDriver | EC.WebElement, xpath: str, by=By.XPA
43
45
  return root.find_element(by, xpath)
44
46
 
45
47
 
46
- def run_test_conversion_with_gui(test_spec: ConversionTestSpec,
47
- driver: WebDriver,
48
- origin: str):
49
- """Runs a test conversion or series thereof through the GUI. Note that this requires the server to be started before
50
- this is called.
51
-
52
- Parameters
53
- ----------
54
- test_spec : ConversionTestSpec
55
- The specification for the test or series of tests to be run
56
- driver : WebDriver
57
- The WebDriver to be used for testing
58
- origin : str
59
- The address of the homepage of the testing server
60
- """
61
- # Make temporary directories for the input and output files to be stored in
62
- with TemporaryDirectory("_input") as input_dir, TemporaryDirectory("_output") as output_dir:
63
- # Iterate over the test spec to run each individual test it defines
64
- for single_test_spec in test_spec:
65
- if single_test_spec.skip:
66
- print(f"Skipping single test spec {single_test_spec}")
67
- continue
68
- print(f"Running single test spec: {single_test_spec}")
69
- _run_single_test_conversion_with_gui(test_spec=single_test_spec,
70
- input_dir=input_dir,
71
- output_dir=output_dir,
72
- driver=driver,
73
- origin=origin)
74
- print(f"Success for test spec: {single_test_spec}")
75
-
76
-
77
- def _run_single_test_conversion_with_gui(test_spec: SingleConversionTestSpec,
78
- input_dir: str,
79
- output_dir: str,
80
- driver: WebDriver,
81
- origin: str):
82
- """Runs a single test conversion through the GUI.
83
-
84
- Parameters
85
- ----------
86
- test_spec : SingleConversionTestSpec
87
- The specification for the test to be run
88
- input_dir : str
89
- A directory which can be used to store input data before uploading
90
- output_dir : str
91
- A directory which can be used to create output data after downloading
92
- driver : WebDriver
93
- The WebDriver to be used for testing
94
- origin : str
95
- The address of the homepage of the testing server
96
- """
48
+ def wait_for_cover_hidden(root: WebDriver):
49
+ """Wait until the page cover is removed"""
50
+ WebDriverWait(root, TIMEOUT).until(EC.invisibility_of_element((By.XPATH, "//div[@id='cover']")))
97
51
 
98
- exc_info: pytest.ExceptionInfo | None = None
99
- if test_spec.expect_success:
100
- try:
101
- run_converter_through_gui(test_spec=test_spec,
102
- input_dir=input_dir,
103
- output_dir=output_dir,
104
- driver=driver,
105
- origin=origin,
106
- **test_spec.conversion_kwargs)
107
- success = True
108
- except Exception:
109
- print(f"Unexpected exception raised for single test spec {test_spec}")
110
- raise
111
- else:
112
- with pytest.raises(FileConverterException) as exc_info:
113
- run_converter_through_gui(test_spec=test_spec,
114
- input_dir=input_dir,
115
- output_dir=output_dir,
116
- driver=driver,
117
- origin=origin,
118
- **test_spec.conversion_kwargs)
119
- success = False
120
-
121
- # Compile output info for the test and call the callback function if one is provided
122
- if test_spec.callback:
123
- test_info = ConversionTestInfo(run_type="gui",
124
- test_spec=test_spec,
125
- input_dir=input_dir,
126
- output_dir=output_dir,
127
- success=success,
128
- exc_info=exc_info)
129
- callback_msg = test_spec.callback(test_info)
130
- assert not callback_msg, callback_msg
131
-
132
-
133
- def run_converter_through_gui(test_spec: SingleConversionTestSpec,
134
- input_dir: str,
135
- output_dir: str,
136
- driver: WebDriver,
137
- origin: str,
138
- **conversion_kwargs):
139
- """_summary_
140
-
141
- Parameters
142
- ----------
143
- test_spec : SingleConversionTestSpec
144
- The specification for the test to be run
145
- input_dir : str
146
- The directory which contains the input file
147
- output_dir : str
148
- The directory which contains the output file
149
- driver : WebDriver
150
- The WebDriver to be used for testing
151
- origin : str
152
- The address of the homepage of the testing server
52
+
53
+ @dataclass
54
+ class GuiTestSpecRunner():
55
+ """Class which provides an interface to run test conversions through the GUI
153
56
  """
154
57
 
155
- # Get just the local filename
156
- filename = os.path.split(test_spec.filename)[1]
157
-
158
- # Default options for conversion
159
- base_filename, from_format = split_archive_ext(filename)
160
- strict = True
161
- from_flags: str | None = None
162
- to_flags: str | None = None
163
- from_options: str | None = None
164
- to_options: str | None = None
165
- coord_gen = None
166
- coord_gen_qual = None
167
-
168
- # For each argument in the conversion kwargs, interpret it as the appropriate option for this conversion, overriding
169
- # defaults set above
170
- for key, val in conversion_kwargs.items():
171
- if key == "from_format":
172
- from_format = val
173
- elif key == "log_mode":
174
- raise ValueError(f"The conversion kwarg {key} is not valid with conversions through the GUI")
175
- elif key == "delete_input":
176
- raise ValueError(f"The conversion kwarg {key} is not valid with conversions through the GUI")
177
- elif key == "strict":
178
- strict = val
179
- elif key == "max_file_size":
180
- raise ValueError(f"The conversion kwarg {key} is not valid with conversions through the GUI")
181
- elif key == "data":
182
- for subkey, subval in val.items():
183
- if subkey == "from_flags":
184
- from_flags = subval
185
- elif subkey == "to_flags":
186
- to_flags = subval
187
- elif subkey == "from_options":
188
- from_options = subval
189
- elif subkey == "to_options":
190
- to_options = subval
191
- elif subkey == COORD_GEN_KEY:
192
- coord_gen = subval
193
- if COORD_GEN_QUAL_KEY in val:
194
- coord_gen_qual = val[COORD_GEN_QUAL_KEY]
195
- else:
196
- coord_gen_qual = DEFAULT_COORD_GEN_QUAL
197
- elif subkey == COORD_GEN_QUAL_KEY:
198
- # Handled alongside COORD_GEN_KEY above
199
- pass
200
- else:
201
- pytest.fail(f"The key 'data[\"{subkey}\"]' was passed to `conversion_kwargs` but could not be "
202
- "interpreted")
203
- else:
204
- pytest.fail(f"The key '{key}' was passed to `conversion_kwargs` but could not be interpreted")
58
+ driver: WebDriver
59
+ """The WebDriver to be used for testing"""
60
+
61
+ origin: str = DEFAULT_ORIGIN
62
+ """The address of the homepage of the testing server"""
205
63
 
206
- # Cleanup of arguments
207
- if from_format.startswith("."):
208
- from_format = from_format[1:]
64
+ def run(self, test_spec: ConversionTestSpec):
65
+ """Run the test conversions outlined in a test spec"""
66
+
67
+ self._test_spec = test_spec
68
+
69
+ # Make temporary directories for the input and output files to be stored in
70
+ with TemporaryDirectory("_input") as input_dir, TemporaryDirectory("_output") as output_dir:
71
+
72
+ # Iterate over the test spec to run each individual test it defines
73
+ for single_test_spec in test_spec:
74
+ if single_test_spec.skip:
75
+ print(f"Skipping single test spec {single_test_spec}")
76
+ continue
209
77
 
210
- # Set up the input file where we expect it to be
211
- source_input_file = os.path.realpath(os.path.join(get_input_test_data_loc(), test_spec.filename))
212
- input_file = os.path.join(input_dir, test_spec.filename)
213
- if (os.path.isfile(input_file)):
214
- os.unlink(input_file)
215
- os.symlink(source_input_file, input_file)
78
+ print(f"Running single test spec: {single_test_spec}")
216
79
 
217
- # Remove test files from Downloads directory if they exist.
80
+ GuiSingleTestSpecRunner(parent=self,
81
+ input_dir=input_dir,
82
+ output_dir=output_dir,
83
+ single_test_spec=single_test_spec).run()
218
84
 
219
- log_file = os.path.realpath(os.path.join(os.path.expanduser("~/Downloads"), test_spec.log_filename))
220
- if (os.path.isfile(log_file)):
221
- os.remove(log_file)
85
+ print(f"Success for test spec: {single_test_spec}")
222
86
 
223
- output_file = os.path.realpath(os.path.join(os.path.expanduser("~/Downloads"), test_spec.out_filename))
224
- if (os.path.isfile(output_file)):
225
- os.remove(output_file)
226
87
 
227
- # Get the homepage
228
- driver.get(f"{origin}/")
88
+ class GuiSingleTestSpecRunner:
89
+ """Class which handles running an individual test conversion
90
+ """
229
91
 
230
- wait_for_element(driver, "//select[@id='fromList']/option")
92
+ def __init__(self,
93
+ parent: GuiTestSpecRunner,
94
+ input_dir: str,
95
+ output_dir: str,
96
+ single_test_spec: SingleConversionTestSpec):
97
+ """
98
+
99
+ Parameters
100
+ ----------
101
+ parent : GuiTestSpecRunner
102
+ The GuiTestSpecRunner which created this and is running it
103
+ input_dir : str
104
+ The temporary directory to be used for input data
105
+ output_dir : str
106
+ The temporary directory to be used for output data
107
+ single_test_spec : SingleConversionTestSpec
108
+ The test spec that is currently being tested
109
+ """
110
+
111
+ self.input_dir: str = input_dir
112
+ self.output_dir: str = output_dir
113
+ self.single_test_spec: SingleConversionTestSpec = single_test_spec
114
+
115
+ # Inherit data from the parent class
116
+
117
+ self.driver: WebDriver = parent.driver
118
+ """The WebDriver to be used for testing"""
119
+
120
+ self.origin: str = parent.origin
121
+ """The address of the homepage of the testing server"""
122
+
123
+ # Interpret information from the test spec that we'll need for testing
124
+
125
+ # Get just the local filename
126
+ self._filename = os.path.split(self.single_test_spec.filename)[1]
127
+
128
+ # Default options for conversion
129
+ self._base_filename, ext = split_archive_ext(self._filename)
130
+ self._strict = True
131
+ self._from_flags: str | None = None
132
+ self._to_flags: str | None = None
133
+ self._from_options: str | None = None
134
+ self._to_options: str | None = None
135
+ self._coord_gen = None
136
+ self._coord_gen_qual = None
137
+
138
+ # Get the from_format from the extension if not provided
139
+ from_format = single_test_spec.from_format
140
+ if not from_format:
141
+ from_format = ext
142
+
143
+ # Get the format info for each format, which we'll use to get the name and note of each
144
+ self._from_format_info = get_format_info(from_format, which=0)
145
+ self._to_format_info = get_format_info(single_test_spec.to_format, which=0)
146
+
147
+ # For each argument in the conversion kwargs, interpret it as the appropriate option for this conversion,
148
+ # overriding defaults set above
149
+ for key, val in self.single_test_spec.conversion_kwargs.items():
150
+ if key == "log_mode":
151
+ raise ValueError(f"The conversion kwarg {key} is not valid with conversions through the GUI")
152
+ elif key == "delete_input":
153
+ raise ValueError(f"The conversion kwarg {key} is not valid with conversions through the GUI")
154
+ elif key == "strict":
155
+ self._strict = val
156
+ elif key == "max_file_size":
157
+ raise ValueError(f"The conversion kwarg {key} is not valid with conversions through the GUI")
158
+ elif key == "data":
159
+ for subkey, subval in val.items():
160
+ if subkey == "from_flags":
161
+ self._from_flags = subval
162
+ elif subkey == "to_flags":
163
+ self._to_flags = subval
164
+ elif subkey == "from_options":
165
+ self._from_options = subval
166
+ elif subkey == "to_options":
167
+ self._to_options = subval
168
+ elif subkey == COORD_GEN_KEY:
169
+ self._coord_gen = subval
170
+ if COORD_GEN_QUAL_KEY in val:
171
+ self._coord_gen_qual = val[COORD_GEN_QUAL_KEY]
172
+ else:
173
+ self._coord_gen_qual = DEFAULT_COORD_GEN_QUAL
174
+ elif subkey == COORD_GEN_QUAL_KEY:
175
+ # Handled alongside COORD_GEN_KEY above
176
+ pass
177
+ else:
178
+ pytest.fail(f"The key 'data[\"{subkey}\"]' was passed to `conversion_kwargs` but could not be "
179
+ "interpreted")
180
+ else:
181
+ pytest.fail(f"The key '{key}' was passed to `conversion_kwargs` but could not be interpreted")
182
+
183
+ def run(self):
184
+ """Run the conversion outlined in the test spec"""
185
+
186
+ exc_info: pytest.ExceptionInfo | None = None
187
+ if self.single_test_spec.expect_success:
188
+ try:
189
+ self._run_conversion()
190
+ success = False
191
+ except Exception:
192
+ print(f"Unexpected exception raised for single test spec {self.single_test_spec}")
193
+ raise
194
+ else:
195
+ with pytest.raises(FileConverterException) as exc_info:
196
+ self._run_conversion()
197
+ success = False
198
+
199
+ # Compile output info for the test and call the callback function if one is provided
200
+ if self.single_test_spec.callback:
201
+ test_info = ConversionTestInfo(run_type="gui",
202
+ test_spec=self.single_test_spec,
203
+ input_dir=self.input_dir,
204
+ output_dir=self.output_dir,
205
+ success=success,
206
+ exc_info=exc_info)
207
+ callback_msg = self.single_test_spec.callback(test_info)
208
+ if callback_msg:
209
+ pytest.fail(callback_msg)
210
+
211
+ def _run_conversion(self):
212
+ """Run a conversion through the GUI
213
+ """
214
+
215
+ self._set_up_files()
216
+
217
+ self._select_formats_and_converter()
218
+
219
+ self._set_conversion_settings()
220
+
221
+ self._provide_input_file()
222
+
223
+ self._request_conversion()
224
+
225
+ self._move_output()
226
+
227
+ def _set_up_files(self):
228
+ """Set up the filenames we expect and initialize them - delete any leftover files and symlink the input file
229
+ to the desired location
230
+ """
231
+ # Set up the expected filenames
232
+ source_input_file = os.path.realpath(os.path.join(get_input_test_data_loc(), self.single_test_spec.filename))
233
+ self._input_file = os.path.join(self.input_dir, self.single_test_spec.filename)
234
+
235
+ self._log_file = os.path.realpath(os.path.join(os.path.expanduser("~/Downloads"),
236
+ self.single_test_spec.log_filename))
237
+
238
+ self._output_file = os.path.realpath(os.path.join(os.path.expanduser("~/Downloads"),
239
+ self.single_test_spec.out_filename))
240
+
241
+ # Clean up any leftover files
242
+ if (os.path.isfile(self._input_file)):
243
+ os.unlink(self._input_file)
244
+ if (os.path.isfile(self._log_file)):
245
+ os.remove(self._log_file)
246
+ if (os.path.isfile(self._output_file)):
247
+ os.remove(self._output_file)
248
+
249
+ # Symlink the input file to the desired location
250
+ os.symlink(source_input_file, self._input_file)
251
+
252
+ def _select_formats_and_converter(self):
253
+ """Handle the tasks on the format and converter selection page when running a test:
254
+
255
+ 1. Load the main page (waiting for it to fully load)
256
+ 2. Select the input and output formats
257
+ 3. Select the converter
258
+ 4. Click the "Yes" button to confirm and go to the convert page
259
+ """
260
+
261
+ # Get the homepage and wait for the cover to be removed
262
+ self.driver.get(f"{self.origin}/")
263
+ wait_for_cover_hidden(self.driver)
264
+
265
+ wait_for_element(self.driver, "//select[@id='fromList']/option")
266
+
267
+ # Select from_format from the 'from' list.
268
+ full_from_format = f"{self._from_format_info.name}: {self._from_format_info.note}"
269
+ self.driver.find_element(
270
+ By.XPATH, f"//select[@id='fromList']/option[starts-with(.,'{full_from_format}')]").click()
271
+
272
+ # Select to_format from the 'to' list.
273
+ full_to_format = f"{self._to_format_info.name}: {self._to_format_info.note}"
274
+ self.driver.find_element(
275
+ By.XPATH, f"//select[@id='toList']/option[starts-with(.,'{full_to_format}')]").click()
276
+
277
+ # Select converter from the available conversion options list.
278
+ self.driver.find_element(
279
+ By.XPATH, f"//select[@id='success']/option[contains(.,'{self.single_test_spec.converter_name}')]").click()
280
+
281
+ # Click on the "Yes" button to accept the converter and go to the conversion page, and wait for the cover to be
282
+ # removed there
283
+ self.driver.find_element(By.XPATH, "//input[@id='yesButton']").click()
284
+ wait_for_cover_hidden(self.driver)
285
+
286
+ def _set_conversion_settings(self):
287
+ """Set settings on the convert page appropriately for the desired conversion
288
+ """
289
+ # Request non-strict filename checking if desired
290
+ if not self._strict:
291
+ wait_and_find_element(self.driver, "//input[@id='extCheck']").click()
292
+
293
+ # Request the log file
294
+ wait_and_find_element(self.driver, "//input[@id='requestLog']").click()
295
+
296
+ # Set appropriate format and converter settings for this conversion
297
+ self._select_format_flags()
298
+ self._set_format_options()
299
+ self._apply_radio_settings()
300
+
301
+ def _provide_input_file(self):
302
+ """Provide the input file for the conversion, checking if any alert is raised in response
303
+ """
304
+ # Select the input file
305
+ wait_and_find_element(self.driver, "//input[@id='fileToUpload']").send_keys(str(self._input_file))
231
306
 
232
- # Select from_format from the 'from' list.
233
- driver.find_element(By.XPATH, f"//select[@id='fromList']/option[starts-with(.,'{from_format}:')]").click()
307
+ # An alert may be present here, which we check for using a try block
308
+ try:
309
+ WebDriverWait(self.driver, 0.2).until(EC.alert_is_present())
310
+ alert = Alert(self.driver)
311
+ alert_text = alert.text
312
+ alert.dismiss()
313
+ raise FileConverterInputException(alert_text)
314
+ except TimeoutException:
315
+ pass
316
+
317
+ def _select_format_flags(self):
318
+ """Select desired format flags. The options in the select box only have a text attribute, so we need to find
319
+ the one that starts with each flag - since we don't have too many, iterating over all possible combinations is
320
+ the easiest way
321
+ """
322
+ for (l_flags, select_id) in ((self._from_flags, "inFlags"),
323
+ (self._to_flags, "outFlags")):
324
+ if not l_flags:
325
+ continue
326
+ flags_select = Select(wait_and_find_element(self.driver, f"//select[@id='{select_id}']"))
327
+ for flag in l_flags:
328
+ for option in flags_select.options:
329
+ if option.text.startswith(f"{flag}:"):
330
+ flags_select.select_by_visible_text(option.text)
331
+ break
332
+ else:
333
+ raise ValueError(f"Flag {flag} was not found in {select_id} selection box for conversion from "
334
+ f"{self._from_format_info.name} to {self._to_format_info.name} with "
335
+ f"converter {self.single_test_spec.converter_name}")
336
+
337
+ def _set_format_options(self):
338
+ """Set desired format options
339
+ """
340
+ for (options_string, table_id) in ((self._from_options, "in_argFlags"),
341
+ (self._to_options, "out_argFlags")):
342
+ if not options_string:
343
+ continue
234
344
 
235
- # Select to_format from the 'to' list.
236
- driver.find_element(By.XPATH, f"//select[@id='toList']/option[starts-with(.,'{test_spec.to_format}:')]").click()
345
+ # Split each option into words, of which the first letter of each is the key and the remainder is the value
346
+ l_options = options_string.split()
237
347
 
238
- # Select converter from the available conversion options list.
239
- driver.find_element(By.XPATH, f"//select[@id='success']/option[contains(.,'{test_spec.converter_name}')]").click()
348
+ # Get the rows in the options table
349
+ options_table = wait_and_find_element(self.driver, f"//table[@id='{table_id}']")
350
+ l_rows = options_table.find_elements(By.XPATH, "./tr")
240
351
 
241
- # Click on the "Yes" button to accept the converter and go to the conversion page
242
- driver.find_element(By.XPATH, "//input[@id='yesButton']").click()
352
+ # Look for and set each option
353
+ for option in l_options:
354
+ for row in l_rows:
355
+ l_items = row.find_elements(By.XPATH, "./td")
356
+ label = l_items[1]
357
+ if not label.text.startswith(option[0]):
358
+ continue
243
359
 
244
- # Request non-strict filename checking if desired
245
- if not strict:
246
- wait_and_find_element(driver, "//input[@id='extCheck']").click()
360
+ # Select the option by clicking the box at the first element in the row to make the input appear
361
+ l_items[0].click()
247
362
 
248
- # Select the input file
249
- wait_and_find_element(driver, "//input[@id='fileToUpload']").send_keys(str(input_file))
363
+ # Input the option in the input box that appears in the third position in the row
364
+ input_box = wait_and_find_element(l_items[2], "./input")
365
+ input_box.send_keys(option[1:])
250
366
 
251
- # An alert may be present here, which we check for using a try block
252
- try:
253
- WebDriverWait(driver, 0.2).until(EC.alert_is_present())
254
- alert = Alert(driver)
255
- alert_text = alert.text
256
- alert.dismiss()
257
- raise FileConverterInputException(alert_text)
258
- except TimeoutException:
259
- pass
260
-
261
- # Request the log file
262
- wait_and_find_element(driver, "//input[@id='requestLog']").click()
263
-
264
- # Select appropriate format args. The args only have a text attribute, so we need to find the one that starts with
265
- # each flag - since we don't have too many, iterating over all possible combinations is the easiest way
266
- for (l_flags, select_id) in ((from_flags, "inFlags"),
267
- (to_flags, "outFlags")):
268
- if not l_flags:
269
- continue
270
- flags_select = Select(wait_and_find_element(driver, f"//select[@id='{select_id}']"))
271
- for flag in l_flags:
272
- found = False
273
- for option in flags_select.options:
274
- if option.text.startswith(f"{flag}:"):
275
- flags_select.select_by_visible_text(option.text)
276
- found = True
277
367
  break
278
- if not found:
279
- raise ValueError(f"Flag {flag} was not found in {select_id} selection box for conversion from "
280
- f"{from_format} to {test_spec.to_format} with converter {test_spec.converter_name}")
281
-
282
- for (options_string, table_id) in ((from_options, "in_argFlags"),
283
- (to_options, "out_argFlags")):
284
- if not options_string:
285
- continue
286
-
287
- # Split each option into words, of which the first letter of each is the key and the remainder is the value
288
- l_options = options_string.split()
289
-
290
- # Get the rows in the options table
291
- options_table = wait_and_find_element(driver, f"//table[@id='{table_id}']")
292
- l_rows = options_table.find_elements(By.XPATH, "./tr")
293
-
294
- # Look for and set each option
295
- for option in l_options:
296
- found = False
297
- for row in l_rows:
298
- l_items = row.find_elements(By.XPATH, "./td")
299
- label = l_items[1]
300
- if not label.text.startswith(option[0]):
301
- continue
302
368
 
303
- # Select the option by clicking the box at the first element in the row to make the input appear
304
- l_items[0].click()
305
-
306
- # Input the option in the input box that appears in the third position in the row
307
- input_box = wait_and_find_element(l_items[2], "./input")
308
- input_box.send_keys(option[1:])
369
+ else:
370
+ raise ValueError(f"Option {option} was not found in {table_id} options table for conversion from "
371
+ f"{self._from_format_info.name} to {self._to_format_info.name} with "
372
+ f"converter {self.single_test_spec.converter_name}")
309
373
 
310
- found = True
311
- break
374
+ def _apply_radio_settings(self):
375
+ """Apply any radio-button settings desired for this conversion by clicking the appropriate radio buttons
376
+ """
312
377
 
313
- if not found:
314
- raise ValueError(f"Option {option} was not found in {table_id} options table for conversion from "
315
- f"{from_format} to {test_spec.to_format} with converter {test_spec.converter_name}")
378
+ for setting, name, l_allowed in ((self._coord_gen, "coord_gen", L_ALLOWED_COORD_GENS),
379
+ (self._coord_gen_qual, "coord_gen_qual", L_ALLOWED_COORD_GEN_QUALS)):
380
+ if not setting:
381
+ continue
316
382
 
317
- # If radio-button settings are supplied, apply them now
318
- for setting, name, l_allowed in ((coord_gen, "coord_gen", L_ALLOWED_COORD_GENS),
319
- (coord_gen_qual, "coord_gen_qual", L_ALLOWED_COORD_GEN_QUALS)):
320
- if not setting:
321
- continue
383
+ if setting not in l_allowed:
384
+ raise ValueError(f"Invalid {name} value supplied: {setting}. Allowed values are: " +
385
+ str(l_allowed))
322
386
 
323
- if setting not in l_allowed:
324
- raise ValueError(f"Invalid {name} value supplied: {setting}. Allowed values are: " +
325
- str(l_allowed))
387
+ setting_radio = wait_and_find_element(self.driver, f"//input[@value='{setting}']")
388
+ setting_radio.click()
326
389
 
327
- setting_radio = wait_and_find_element(driver, f"//input[@value='{setting}']")
328
- setting_radio.click()
390
+ def _request_conversion(self):
391
+ """Request the conversion, handle the alert box that appears, and wait for the files to be downloaded
392
+ """
393
+ # Click on the "Convert" button.
394
+ wait_and_find_element(self.driver, "//input[@id='uploadButton']").click()
329
395
 
330
- # Click on the "Convert" button.
331
- wait_and_find_element(driver, "//input[@id='uploadButton']").click()
396
+ # Handle alert box.
397
+ WebDriverWait(self.driver, TIMEOUT).until(EC.alert_is_present())
398
+ alert = Alert(self.driver)
399
+ alert_text = alert.text
400
+ alert.dismiss()
332
401
 
333
- # Handle alert box.
334
- WebDriverWait(driver, TIMEOUT).until(EC.alert_is_present())
335
- alert = Alert(driver)
336
- alert_text = alert.text
337
- alert.dismiss()
402
+ if alert_text.startswith("ERROR:"):
403
+ # Raise an appropriate exception type depending on if it's a recognised input issue or not
404
+ if "unexpected exception" in alert_text:
405
+ raise FileConverterAbortException(STATUS_CODE_GENERAL, alert_text)
406
+ raise FileConverterInputException(alert_text)
338
407
 
339
- if alert_text.startswith("ERROR:"):
340
- # Raise an appropriate exception type depending on if it's a recognised input issue or not
341
- if "unexpected exception" in alert_text:
342
- raise FileConverterAbortException(STATUS_CODE_GENERAL, alert_text)
343
- raise FileConverterInputException(alert_text)
408
+ # Wait until the log file exists, since it's downloaded second
409
+ time_elapsed = 0
410
+ while not os.path.isfile(self._log_file):
411
+ time.sleep(1)
412
+ time_elapsed += 1
413
+ if time_elapsed > TIMEOUT:
414
+ pytest.fail(f"Download of {self._output_file} and {self._log_file} timed out")
344
415
 
345
- # Wait until the log file exists, since it's downloaded second
346
- time_elapsed = 0
347
- while not os.path.isfile(log_file):
348
416
  time.sleep(1)
349
- time_elapsed += 1
350
- if time_elapsed > TIMEOUT:
351
- pytest.fail(f"Download of {output_file} and {log_file} timed out")
352
-
353
- time.sleep(1)
354
-
355
- if not os.path.isfile(output_file):
356
- raise FileConverterAbortException("ERROR: No output file was produced. Log contents:\n" +
357
- open(log_file, "r").read())
358
-
359
- # Move the output file and log file to the expected locations
360
- for qual_filename in output_file, log_file:
361
- base_filename = os.path.split(qual_filename)[1]
362
- target_filename = os.path.join(output_dir, base_filename)
363
- if os.path.isfile(target_filename):
364
- os.remove(target_filename)
365
- if os.path.isfile(qual_filename):
366
- shutil.move(qual_filename, target_filename)
417
+
418
+ def _move_output(self):
419
+ """Move the created output files out of the default Downloads directory and into the desired output files
420
+ directory.
421
+ """
422
+ # Check for the presence of the output file
423
+ if not os.path.isfile(self._output_file):
424
+ raise FileConverterAbortException("ERROR: No output file was produced. Log contents:\n" +
425
+ open(self._log_file, "r").read())
426
+
427
+ # Move the output file and log file to the expected locations
428
+ for qual_filename in self._output_file, self._log_file:
429
+ self._base_filename = os.path.split(qual_filename)[1]
430
+ target_filename = os.path.join(self.output_dir, self._base_filename)
431
+ if os.path.isfile(target_filename):
432
+ os.remove(target_filename)
433
+ if os.path.isfile(qual_filename):
434
+ shutil.move(qual_filename, target_filename)