Add project files.
This commit is contained in:
145
Soak/Selenium.cs
Normal file
145
Soak/Selenium.cs
Normal file
@@ -0,0 +1,145 @@
|
||||
using OpenQA.Selenium;
|
||||
using OpenQA.Selenium.Chrome;
|
||||
using OpenQA.Selenium.Support.UI;
|
||||
|
||||
namespace AiQ_GUI
|
||||
{
|
||||
internal class Selenium
|
||||
{
|
||||
// Directs to url using chrome drive and waits for the page to fully load
|
||||
public static void GoToUrl(string url, ChromeDriver driver)
|
||||
{
|
||||
try
|
||||
{
|
||||
driver.Navigate().GoToUrl(url);
|
||||
|
||||
// Wait until the document is fully loaded
|
||||
WebDriverWait wait = new(driver, TimeSpan.FromSeconds(10));
|
||||
wait.Until(d => ((IJavaScriptExecutor)d).ExecuteScript("return document.readyState").Equals("complete"));
|
||||
}
|
||||
catch (WebDriverTimeoutException)
|
||||
{
|
||||
MainForm.Instance.AddToActionsList("Could not load web page " + url + "1. Check camera has no password set. " + Environment.NewLine + "2. If unable to fix speak to supervisor.");
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieves lates element ID for Shutter,iris, gain and IR
|
||||
public static ElementID GetElementIds(ChromeDriver driver)
|
||||
{
|
||||
ElementID elementID = new()
|
||||
{
|
||||
modeId = GetLatestElementIdContaining("Mode_", driver),
|
||||
shutterId = GetLatestElementIdContaining("FixShutter_", driver),
|
||||
irisId = GetLatestElementIdContaining("FixIris_", driver),
|
||||
gainId = GetLatestElementIdContaining("FixGain_", driver),
|
||||
irLevelId = GetLatestElementIdContaining("CameraControls_", driver),
|
||||
CamAct = GetLatestElementIdContaining("CameraActivity_", driver)
|
||||
};
|
||||
|
||||
return elementID;
|
||||
}
|
||||
|
||||
// Clicks the element with the specified ID using Chromedriver
|
||||
public static void ClickElementByID(string elementID, ChromeDriver driver)
|
||||
{
|
||||
ClickElementByID(elementID, true, driver);
|
||||
}
|
||||
|
||||
// Attempts to click the web element with the specified ID; refreshes the page and retries once if the initial attempt fails
|
||||
public static void ClickElementByID(string elementID, bool tryagain, ChromeDriver driver)
|
||||
{
|
||||
try
|
||||
{
|
||||
WebDriverWait wait = new(driver, TimeSpan.FromSeconds(10));
|
||||
IWebElement element = wait.Until(driver => driver.FindElement(By.Id(elementID)));
|
||||
element.Click();
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (tryagain)
|
||||
{
|
||||
driver.Navigate().Refresh();
|
||||
WebDriverWait wait = new(driver, TimeSpan.FromSeconds(10));
|
||||
wait.Until(d => ((IJavaScriptExecutor)d).ExecuteScript("return document.readyState").Equals("complete"));
|
||||
ClickElementByID(elementID, false, driver);
|
||||
}
|
||||
|
||||
MainForm.Instance.AddToActionsList("Could not click " + elementID);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialises and opens a ChromeDriver with specific options
|
||||
public static ChromeDriver OpenDriver()
|
||||
{
|
||||
ChromeDriverService chromeDriverService = ChromeDriverService.CreateDefaultService();
|
||||
chromeDriverService.HideCommandPromptWindow = true;
|
||||
ChromeOptions options = new();
|
||||
options.AddArguments("--app=data:,", "--window-size=960,1040", "--window-position=0,0");
|
||||
options.AddExcludedArgument("enable-automation");
|
||||
options.AddAdditionalChromeOption("useAutomationExtension", false);
|
||||
|
||||
ChromeDriver driver = new(chromeDriverService, options);
|
||||
driver.Manage().Timeouts().PageLoad = TimeSpan.FromSeconds(5);
|
||||
return driver;
|
||||
}
|
||||
|
||||
// Changes the value of a dropdown element by ID, logs the action, and verifies the result using flashline feedback
|
||||
public static async Task Dropdown_Change(string fullId, string value, ChromeDriver driver, string SoakLogFile, string CamAct)
|
||||
{
|
||||
WebDriverWait wait = new(driver, TimeSpan.FromSeconds(10));
|
||||
IWebElement element = wait.Until(driver => driver.FindElement(By.Id(fullId)));
|
||||
|
||||
await Logging.LogMessage($"Changing dropdown {fullId} to value: {value}", SoakLogFile);
|
||||
await Task.Delay(fullId.Contains("Mode_") ? 4000 : 200);
|
||||
|
||||
if (value == element.GetAttribute("value"))
|
||||
return; // No change needed setting is already correct
|
||||
|
||||
SelectElement select = new(element);
|
||||
|
||||
select.SelectByValue(value);
|
||||
await Task.Delay(500); // Wait for the change to take effect
|
||||
|
||||
if (!await Checkflashline(driver, CamAct))
|
||||
{
|
||||
Logging.LogWarningMessage("Bad flashline after changing: " + fullId, SoakLogFile);
|
||||
MainForm.Instance.AddToActionsList("Bad flashline after changing: " + fullId);
|
||||
}
|
||||
}
|
||||
|
||||
// Monitors the flashline element's color to determine success (green) or failure (red) of a camera
|
||||
public static async Task<bool> Checkflashline(ChromeDriver driver, string CamAct)
|
||||
{
|
||||
IWebElement flashline = driver.FindElement(By.Id(CamAct));
|
||||
string flashlinecolor = flashline.GetCssValue("color");
|
||||
|
||||
Task FlashWait = Task.Delay(8000);
|
||||
|
||||
while (!FlashWait.IsCompleted)
|
||||
{
|
||||
if (flashlinecolor.Contains("0, 255, 127"))
|
||||
return true;
|
||||
else if (flashlinecolor.Contains("255, 69, 0"))
|
||||
return false;
|
||||
|
||||
await Task.Delay(500);
|
||||
flashlinecolor = flashline.GetCssValue("color");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Retrieves the ID of the last <select> element whose ID starts with the specified partial ID
|
||||
public static string GetLatestElementIdContaining(string partialId, ChromeDriver driver)
|
||||
{
|
||||
System.Collections.ObjectModel.ReadOnlyCollection<IWebElement> elements = driver.FindElements(By.XPath($"//*[@id][starts-with(@id, '{partialId}')]"));
|
||||
|
||||
List<string?> matchingIds = elements
|
||||
.Select(el => el.GetAttribute("id"))
|
||||
.Where(id => !string.IsNullOrEmpty(id))
|
||||
.ToList();
|
||||
|
||||
return matchingIds.LastOrDefault();
|
||||
}
|
||||
}
|
||||
}
|
332
Soak/SoakTest.cs
Normal file
332
Soak/SoakTest.cs
Normal file
@@ -0,0 +1,332 @@
|
||||
using OpenQA.Selenium;
|
||||
using OpenQA.Selenium.Chrome;
|
||||
using Image = System.Drawing.Image;
|
||||
|
||||
namespace AiQ_GUI
|
||||
{
|
||||
internal class SoakTest
|
||||
{
|
||||
// Main soak test loop: Randomise dropdowns, run luminance test every hour, power cycle at 7am
|
||||
public static async Task StartSoak(Camera CamInfo, CancellationToken token)
|
||||
{
|
||||
if (CamInfo.Serial == "N/A")
|
||||
CamInfo.Serial = "UNKNOWN"; // If serial is not set, set it to UNKNOWN. Cannot have N/A in file names.
|
||||
|
||||
string SoakLogFile = $"SoakLog_{CamInfo.Serial}_{CamInfo.Model}.log";
|
||||
ChromeDriver driver = null;
|
||||
|
||||
try
|
||||
{
|
||||
driver = Selenium.OpenDriver();
|
||||
|
||||
// Keep retrying until connected or cancelled
|
||||
bool connected = false;
|
||||
while (!connected && !token.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Attempt initial connection and navigation to setup tab
|
||||
Selenium.GoToUrl($"http://{CamInfo.IP}", driver);
|
||||
Selenium.ClickElementByID("tabSetup", driver);
|
||||
connected = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.LogErrorMessage($"Initial connection failed: {ex.Message}", SoakLogFile);
|
||||
MainForm.Instance.AddToActionsList($"[{CamInfo.IP}] Initial connection failed: {ex.Message}");
|
||||
|
||||
// Wait 10 seconds before trying again
|
||||
await Task.Delay(TimeSpan.FromSeconds(10), token);
|
||||
}
|
||||
}
|
||||
|
||||
ElementID elementID = null;
|
||||
try
|
||||
{
|
||||
// Try to retrieve all required element IDs from the UI
|
||||
elementID = Selenium.GetElementIds(driver);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.LogErrorMessage($"Failed to get element IDs: {ex.Message}", SoakLogFile);
|
||||
return;
|
||||
}
|
||||
|
||||
int lastHour = DateTime.Now.Hour;
|
||||
|
||||
while (!token.IsCancellationRequested)
|
||||
{
|
||||
int currentHour = DateTime.Now.Hour;
|
||||
|
||||
// At 7am, power cycle the camera
|
||||
if (currentHour == 7 && lastHour != 7)
|
||||
{
|
||||
Logging.LogMessage($"7am detected, restarting camera via /api/restart-hardware.", SoakLogFile);
|
||||
try
|
||||
{
|
||||
await FlexiAPI.APIHTTPRequest("/api/restart-hardware", CamInfo.IP);
|
||||
await Task.Delay(TimeSpan.FromMinutes(4), token); // Wait for restart
|
||||
|
||||
// Retry ping until camera responds or cancelled
|
||||
while (!await Network.PingIP(CamInfo.IP) && !token.IsCancellationRequested)
|
||||
{
|
||||
Logging.LogErrorMessage($"Camera did not respond after restart.", SoakLogFile);
|
||||
await Task.Delay(TimeSpan.FromMinutes(1), token); // Retry after delay of 1 minute
|
||||
}
|
||||
|
||||
if (!token.IsCancellationRequested)
|
||||
{
|
||||
// Reconnect and re-acquire element IDs after restart
|
||||
Selenium.GoToUrl($"http://{CamInfo.IP}", driver);
|
||||
Selenium.ClickElementByID("tabSetup", driver);
|
||||
elementID = Selenium.GetElementIds(driver);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.LogErrorMessage($"Error during power cycle: {ex.Message}", SoakLogFile);
|
||||
}
|
||||
}
|
||||
|
||||
// Every hour, run ImageCheck
|
||||
if (currentHour != lastHour)
|
||||
{
|
||||
Logging.LogMessage($"Hour changed to {currentHour}, running ImageCheck.", SoakLogFile);
|
||||
try
|
||||
{
|
||||
ImageCheck(driver, SoakLogFile, CamInfo.IP, CamInfo.DevPass, elementID);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.LogErrorMessage($"ImageCheck failed: {ex.Message}", SoakLogFile);
|
||||
}
|
||||
lastHour = currentHour;
|
||||
}
|
||||
|
||||
// Every cycle, randomly change dropdowns to simulate interaction
|
||||
try
|
||||
{
|
||||
// If it is auto mode, set it to manual
|
||||
IWebElement modeElement = driver.FindElement(By.Id(elementID.modeId));
|
||||
string selectedText = new OpenQA.Selenium.Support.UI.SelectElement(modeElement).SelectedOption.Text;
|
||||
|
||||
if (selectedText == "Auto")
|
||||
await Selenium.Dropdown_Change(elementID.modeId, "Manual", driver, SoakLogFile, elementID.CamAct);
|
||||
|
||||
await ChangeRandomDropdown(driver, SoakLogFile, elementID);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.LogErrorMessage($"ChangeRandomDropdown failed: {ex.Message}", SoakLogFile);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromSeconds(15), token); // Small delay between each loop iteration
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
break; // Graceful exit when cancellation is requested
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Ensure driver cleanup regardless of success/failure
|
||||
Logging.LogMessage("Driver quiting and ending soak test.", SoakLogFile);
|
||||
driver?.Quit();
|
||||
|
||||
// Create Test report in the same directory as the final test reports.
|
||||
string soakLogPath = LDS.MAVPath + SoakLogFile;
|
||||
|
||||
string SoakTestPath = PDF.CreateSoakTestReport(CamInfo, MainForm.Instance.CbBxUserName.Text, DateTime.Now, soakLogPath);
|
||||
|
||||
if (SoakTestPath != null)
|
||||
{
|
||||
// Delete the soak test log file if the report was created successfully
|
||||
try
|
||||
{
|
||||
if (File.Exists(soakLogPath))
|
||||
File.Delete(soakLogPath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.LogErrorMessage($"Failed to delete soak log file: {ex.Message}{Environment.NewLine}Please delete if you find this.", SoakLogFile);
|
||||
}
|
||||
|
||||
// Find the final test report PDF for this camera
|
||||
string finalTestDir = PDF.TestRecordDir + CamInfo.Model + "\\"; // Directory to final test record
|
||||
string finalTestPattern = $"FinalTestReport_{CamInfo.Model}_{CamInfo.Serial}_*.pdf"; // Pattern to match final test report
|
||||
string finalTestPath = null;
|
||||
|
||||
try
|
||||
{
|
||||
if (Directory.Exists(finalTestDir))
|
||||
{
|
||||
string[] finalTestFiles = Directory.GetFiles(finalTestDir, finalTestPattern);
|
||||
finalTestPath = finalTestFiles.OrderByDescending(f => File.GetCreationTime(f)).FirstOrDefault(); // If multiple, pick the most recent
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MainForm.Instance.AddToActionsList($"Failed to find final test report: {ex.Message}");
|
||||
}
|
||||
|
||||
// Link PDFs if both exist
|
||||
if (!string.IsNullOrEmpty(finalTestPath) && File.Exists(finalTestPath) && !string.IsNullOrEmpty(SoakTestPath) && File.Exists(SoakTestPath))
|
||||
{
|
||||
string outputPath = finalTestDir + $"Final&SoakTestReport_{CamInfo.Model}_{CamInfo.Serial}_{DateTime.Now.ToString("dd-MM-yyyy_HH-mm-ss")}.pdf";
|
||||
try
|
||||
{
|
||||
if (PDF.LinkPDFs(finalTestPath, SoakTestPath, outputPath)) // Delete the separate soak and final test reports if Linking was successful
|
||||
{
|
||||
Logging.LogMessage($"Linked PDFs successfully: {outputPath}");
|
||||
File.Delete(finalTestPath);
|
||||
File.Delete(SoakTestPath);
|
||||
}
|
||||
else
|
||||
MainForm.Instance.AddToActionsList($"Failed to link or delete PDFs");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MainForm.Instance.AddToActionsList($"Failed to link or delete PDFs: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Capture's bright and dark images, then compares their luminance to verify sufficient contrast and adds results to the log
|
||||
public static async Task LuminescenceMean(string FullID, ChromeDriver driver, string[] SettingMinMax, string SoakLogFile, string IP, string DevPass, string CamAct)
|
||||
{
|
||||
string controlType = FullID.Split('_')[0]; // Extract control type from FullID (e.g. "Shutter_1234" → "Shutter")
|
||||
|
||||
// Set bright setting
|
||||
Logging.LogMessage($"Setting {controlType} to bright value: {SettingMinMax[0]}", SoakLogFile);
|
||||
await Selenium.Dropdown_Change(FullID, SettingMinMax[0], driver, SoakLogFile, CamAct);
|
||||
await Task.Delay(500);
|
||||
|
||||
// Take bright image
|
||||
Image ImageBright = await ImageProcessing.GetProcessedImage("Infrared-snapshot", IP, DevPass);
|
||||
if (ImageBright == null)
|
||||
{
|
||||
Logging.LogWarningMessage($"Bright image is null for {controlType} at setting {SettingMinMax[0]}", SoakLogFile);
|
||||
MainForm.Instance.AddToActionsList($"Bright image is null for {controlType} at setting {SettingMinMax[0]}");
|
||||
return;
|
||||
}
|
||||
|
||||
// Set dark setting
|
||||
Logging.LogMessage($"Setting {controlType} to dark value: {SettingMinMax[1]}", SoakLogFile);
|
||||
await Selenium.Dropdown_Change(FullID, SettingMinMax[1], driver, SoakLogFile, CamAct);
|
||||
await Task.Delay(500);
|
||||
|
||||
// Take dark image
|
||||
Image ImageDark = await ImageProcessing.GetProcessedImage("Infrared-snapshot", IP, DevPass);
|
||||
if (ImageDark == null)
|
||||
{
|
||||
Logging.LogWarningMessage($"Dark image is null for {controlType} at setting {SettingMinMax[1]}", SoakLogFile);
|
||||
MainForm.Instance.AddToActionsList($"Dark image is null for {controlType} at setting {SettingMinMax[1]}");
|
||||
return;
|
||||
}
|
||||
|
||||
// Brightness test between min and max settings
|
||||
double Bright_Lum = ImageProcessing.GetMeanLuminance(ImageBright);
|
||||
double Dark_Lum = ImageProcessing.GetMeanLuminance(ImageDark);
|
||||
|
||||
if (Bright_Lum < Dark_Lum * 1.01)
|
||||
{
|
||||
Logging.LogErrorMessage(
|
||||
$"Insufficient luminance contrast. Bright: {Bright_Lum:F2}, Dark: {Dark_Lum:F2} | Type: {controlType} | Bright Setting: {SettingMinMax[0]}, Dark Setting: {SettingMinMax[1]}",
|
||||
SoakLogFile
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logging.LogMessage(
|
||||
$"Sufficient luminance contrast. Bright: {Bright_Lum:F2}, Dark: {Dark_Lum:F2} | Type: {controlType} | Bright Setting: {SettingMinMax[0]}, Dark Setting: {SettingMinMax[1]}",
|
||||
SoakLogFile
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Performs a series of camera control adjustments and luminance checks to verify image settings functionality
|
||||
public async static void ImageCheck(ChromeDriver driver, string SoakLogFile, string IP, string DevPass, ElementID elementID)
|
||||
{
|
||||
await Selenium.Dropdown_Change(elementID.modeId, "Manual", driver, SoakLogFile, elementID.CamAct);
|
||||
await Selenium.Dropdown_Change(elementID.shutterId, "1/1000", driver, SoakLogFile, elementID.CamAct);
|
||||
await Selenium.Dropdown_Change(elementID.gainId, "0dB", driver, SoakLogFile, elementID.CamAct);
|
||||
await Selenium.Dropdown_Change(elementID.irisId, "F4.0", driver, SoakLogFile, elementID.CamAct);
|
||||
await Selenium.Dropdown_Change(elementID.irLevelId, "Safe", driver, SoakLogFile, elementID.CamAct);
|
||||
|
||||
await LuminescenceMean(elementID.shutterId, driver, ["1/100", "1/10000"], SoakLogFile, IP, DevPass, elementID.CamAct); // Check Shutter goes from min to max
|
||||
await Selenium.Dropdown_Change(elementID.shutterId, "1/1000", driver, SoakLogFile, elementID.CamAct); // Reset to default
|
||||
await LuminescenceMean(elementID.irisId, driver, ["F2.0", "F16"], SoakLogFile, IP, DevPass, elementID.CamAct); // Check iris goes from min to max
|
||||
await Selenium.Dropdown_Change(elementID.irisId, "F4.0", driver, SoakLogFile, elementID.CamAct); // Reset to default
|
||||
await LuminescenceMean(elementID.gainId, driver, ["20dB", "0dB"], SoakLogFile, IP, DevPass, elementID.CamAct); // Check gain goes from min to max
|
||||
}
|
||||
|
||||
public async static Task ChangeRandomDropdown(ChromeDriver driver, string SoakLogFile, ElementID elementID)
|
||||
{
|
||||
(string gain, string shutter, string iris, string irLevel) = GetRandoms();
|
||||
await Selenium.Dropdown_Change(elementID.shutterId, shutter, driver, SoakLogFile, elementID.CamAct);
|
||||
await Selenium.Dropdown_Change(elementID.gainId, gain, driver, SoakLogFile, elementID.CamAct);
|
||||
await Selenium.Dropdown_Change(elementID.irisId, iris, driver, SoakLogFile, elementID.CamAct);
|
||||
await Selenium.Dropdown_Change(elementID.irLevelId, irLevel, driver, SoakLogFile, elementID.CamAct);
|
||||
}
|
||||
|
||||
private static (string gain, string shutter, string iris, string irLevel) GetRandoms()
|
||||
{
|
||||
(string[] gainOptions, string[] shutterOptions, string[] irisOptions, string[] irLevelOptions) = GetControlOptions();
|
||||
Random rand = new();
|
||||
|
||||
string iris = "F" + irisOptions[rand.Next(irisOptions.Length)];
|
||||
string irLevel = irLevelOptions[rand.Next(irLevelOptions.Length)];
|
||||
string gain = gainOptions[rand.Next(gainOptions.Length)] + "dB";
|
||||
string shutter = "1/" + shutterOptions[rand.Next(shutterOptions.Length)];
|
||||
|
||||
return (gain, shutter, iris, irLevel);
|
||||
}
|
||||
|
||||
// Helper function to grab the model and serial numbers. Helpful for naming the Soak files and identfying the camera
|
||||
private static (string[] gain, string[] shutter, string[] iris, string[] irLevel) GetControlOptions()
|
||||
{
|
||||
return (
|
||||
new[] { "0", "2", "6", "8", "10", "12", "16", "18", "20", "24" },
|
||||
new[] { "10000", "2000", "1000", "500", "250", "100" },
|
||||
new[] { "2.0", "2.8", "4.0", "5.6", "8.0", "11", "16" },
|
||||
new[] { "Off", "Safe", "Low", "Mid", "High" }
|
||||
);
|
||||
}
|
||||
|
||||
public static CheckBox MakeNewCheckbox(Camera soakInfo, int YLoc)
|
||||
{
|
||||
CheckBox dynamicButton = new()
|
||||
{
|
||||
Location = new Point(12, YLoc),
|
||||
Height = 20,
|
||||
Width = 220,
|
||||
ForeColor = SystemColors.Control,
|
||||
Text = soakInfo.IP + " - " + soakInfo.Serial + " - " + soakInfo.FlexiVersion,
|
||||
Name = "BtnImage" + soakInfo.IP,
|
||||
Checked = true
|
||||
};
|
||||
|
||||
dynamicButton.CheckedChanged += (s, e) =>
|
||||
{
|
||||
soakInfo.IsChecked = dynamicButton.Checked;
|
||||
};
|
||||
|
||||
return dynamicButton;
|
||||
}
|
||||
}
|
||||
|
||||
public class ElementID
|
||||
{
|
||||
public string modeId { get; set; }
|
||||
public string shutterId { get; set; }
|
||||
public string irisId { get; set; }
|
||||
public string gainId { get; set; }
|
||||
public string irLevelId { get; set; }
|
||||
public string CamAct { get; set; }
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user