Job.cs Recipe Book
Table of Contents
Test Structure & Attributes
Basic Job Test Template
[TestFixture (1500, "Nesting Job Tests", Module = "FluxCut", Subsystem = "NestJob")]
internal class Job : IDisposable {
const string TESTMACHINEID1 = "2230001";
const string TESTMACHINEID2 = "97";
const string FXFILE = "123.fx";
[Test (ID, "Test Description", Type = Functional)]
public void Test_CaseID () {
// Test implementation
}
#region Helper Routines
// Helper methods
#endregion
}
Test Attributes
// TestFixture attribute
[TestFixture (1500, "Nesting Job Tests", Module = "FluxCut", Subsystem = "NestJob")]
// Test attribute with type
[Test (401, "Adding and deleting layouts", Type = Functional)]
[Test (402, "Moving layouts", Type = Functional)]
[Test (1528, "Selecting 'New Job' toolbar button opens a pop-up dialog", Type = Functional)]
// Test type options
Type = Functional
IDisposable Pattern Setup
Constructor & Dispose Pattern
// Constructor - Setup phase (runs before first test)
public Job () {
Utils.ConfigureMachine (TESTMACHINEID1); // Machine 2230001
Utils.ConfigureMachine (TESTMACHINEID2); // Machine 97
}
// Dispose - Cleanup phase (runs after all tests)
public void Dispose () {
Utils.UninstallMachine (TESTMACHINEID1);
Utils.UninstallMachine (TESTMACHINEID2);
Utils.DeleteTempFile ();
}
Key Differences from FixtureInitialize/FixtureCleanUp: - Called per test instance (not per fixture) - More flexible resource management - Automatic cleanup via using statement
Machine Configuration
const string TESTMACHINEID1 = "2230001"; // Primary nesting machine
const string TESTMACHINEID2 = "97"; // Secondary laser machine
// Install machines
Utils.ConfigureMachine (TESTMACHINEID1);
Utils.ConfigureMachine (TESTMACHINEID2);
// Uninstall after tests
Utils.UninstallMachine (TESTMACHINEID1);
Utils.UninstallMachine (TESTMACHINEID2);
File Operations
Opening Job Files
// Open job file directly
Utils.OpenJobOrLayout (Utils.tData + "TwinJob.fxjob");
Utils.OpenJobOrLayout (Utils.tData + "NestingJobSample.fxjob");
Utils.OpenJobOrLayout (Utils.tData + "AsymmetricalPolygon.fxjob");
// Open job via helper
OpenJob ("NestingJobSample.fxjob");
OpenJob ("TwinJob.fxjob");
// Open job and Add dialog
OpenAddDialog ("NestingJobSample.fxjob");
File Access Paths
// Test data paths
Utils.tData // Test data directory
Utils.tData + "TwinJob.fxjob"
Utils.tData + "NestingJobSample.fxjob"
Utils.tData + "AsymmetricalPolygon.fxjob"
Utils.tData + "Test_C150733/test.txt"
Utils.tData + "Test_C162420/test_ExportRemSheet.dxf"
// Temporary paths
Utils.tempPath // Temp directory
Utils.tempPath + "test.dxf"
Utils.tempPath + "test.png"
Utils.tempPath + "temp.txt"
Cleanup Operations
// Delete temporary files
Utils.DeleteTempFile ();
// Delete specific file
File.Delete (Utils.tempPath + "test.dxf");
// Delete multiple files
foreach (var file in parts) File.Delete (Utils.tempPath + file);
Job Creation & Management
Creating New Job
// Create new job with empty layout
var job = CreateNewJob ();
// Create with job name
var job = CreateNewJob ("MyJob");
// Internal implementation
internal static AutomationElement? CreateNewJob (string name = "") {
if (!Utils.IsRunning ()) Utils.OpenFlux ();
var mainWin = Utils.MainWin;
mainWin.FindFirstChild ("CmdBar").FindFirstChild (a => a.ByName ("new job")).Click ();
var dlg = mainWin.FindFirstChild (a => a.ByName ("NewJobDlg"));
if (dlg != null && string.IsNullOrEmpty (name)) Utils.Type (name);
dlg?.FindFirstChild (a => a.ByName ("OK").And (a.ByControlType (ControlType.Button))).Click ();
return Utils.FindElement (() => Utils.MainWin.FindFirstChild ("NewJobWindow"));
}
Opening Jobs
// Open Flux first
Utils.OpenFlux ();
// Open job
Utils.OpenJobOrLayout (Utils.tData + filename);
// Get job window
var job = Utils.MainWin.FindFirstChild ("NewJobWindow");
var newJobWindow = GetNewJobWindow (); // With retry logic
Job Window Access
// Get main job window
var job = Utils.MainWin.FindFirstChild ("NewJobWindow");
var job = Utils.FindElement (() => Utils.MainWin.FindFirstChild ("NewJobWindow"));
// Wait for job window to fully load
var newJobWindow = GetNewJobWindow (); // Helper with retry
// Get job window parent
var jobWin = Utils.MainWin.FindFirstDescendant (a => a.ByName ("parts")).Parent;
Job Dialog Operations
// New Job dialog
Utils.MainWin.FindFirstChild ("CmdBar").FindFirstChild (a => a.ByName ("new job")).Click ();
var dlg = Utils.MainWin.FindFirstChild (a => a.ByName ("NewJobDlg"));
// Click OK button
dlg.FindFirstChild (a => a.ByName ("OK").And (a.ByControlType (ControlType.Button))).Click ();
// Click Cancel button
dlg.FindFirstChild (a => a.ByName ("Cancel").And (a.ByControlType (ControlType.Button))).Click ();
// Check if dialog exists
Assert.IsNotNull (dlg, "New job dialog is not found");
Layout Operations
Tab Navigation
// Switch to parts tab
job.FindFirstChild (a => a.ByName ("parts")).Click ();
Utils.FindElement (() => job.FindFirstChild (a => a.ByName ("parts"))).Click ();
// Switch to layouts tab
job.FindFirstChild (a => a.ByName ("layouts")).Click ();
// Switch to sheets tab
job.FindFirstChild (a => a.ByName ("sheets")).Click ();
// Via keyboard shortcut
Utils.Type ('p'); // Parts view
Utils.Type ('l'); // Layouts view
Layout Management
// Access layout controls
var addBtn = job.FindFirstChild ("cAddLayout");
var removeBtn = job.FindFirstChild ("cRemovePattern");
var moveUpBtn = job.FindFirstChild ("cMoveUpBtn");
var moveDownBtn = job.FindFirstChild ("cMoveDownBtn");
// Add layout
addBtn.Click ();
job.FindFirstChild ("cAddLayout").Click ();
// Remove layout
removeBtn.Click ();
job.FindFirstChild ("cRemovePattern").Click ();
// Move layout up
moveUpBtn.Click ();
job.FindFirstChild ("cMoveUpBtn").Click ();
// Move layout down
moveDownBtn.Click ();
job.FindFirstChild ("cMoveDownBtn").Click ();
// Delete all layouts
for (int i = 0; i < 6; i++) removeBtn.Click ();
Layout List Operations
// Get patterns list
var patterns = job.FindFirstChild ("cPatternsList");
// Get all layouts
var layouts = patterns.FindAllChildren ();
// Get layout count
int count = patterns.FindAllChildren ().Length;
// Select layout by index
patterns.FindChildAt (index).Click ();
// Select random layout
patterns.FindChildAt (new Random ().Next (3)).Click ();
// Get first layout
patterns.FindFirstChild ();
// Get layouts via helper
var layouts = Utils.FindElements (() => newJobWindow.FindFirstChild ("cPatternsList").FindAllChildren ());
Nesting Operations
// Renest
job.FindFirstChild ("cNesting").Click ();
// Wait for nesting to complete
WaitTillNestingCompletes ();
WaitTillNestingCompletes (waitTime: 7); // Custom wait time
// Helper implementation
internal static void WaitTillNestingCompletes (int waitTime = 6) {
var newJobWin = Utils.FindElement (() => Utils.MainWin.FindFirstChild ("NewJobWindow"));
Utils.FindElement (() => newJobWin.FindFirstChild ("cPatternsList").FindFirstChild (),
timeout: waitTime, timeOutMsg: "Nesting did not complete within timeout");
}
Layout Switching
// Switch to layout page (full workflow)
private static void SwitchToLayoutPage () {
Utils.Type (ESC);
var newJobWindow = Utils.FindElement (() => Utils.MainWin.FindFirstChild ("NewJobWindow"));
Utils.Type ('l');
Utils.FindElement (() => newJobWindow.FindFirstChild ("cAddLayout"),
timeOutMsg: "Layout page was not opened");
}
Layout Tree Dumping
// Dump automation tree for first layout
Utils.DumpAutomationElement (
jobWin.FindFirstChild ("cPatternsList").FindFirstChild ()
);
// Compare with reference file
Assert.AreTextFilesEqual (
Utils.tData + "Test_C150733/test.txt",
Utils.tempPath + "temp.txt"
);
Part Management
Parts Tab Operations
// Switch to parts tab
job.FindFirstChild (a => a.ByName ("parts")).Click ();
Utils.FindElement (() => job.FindFirstChild (a => a.ByName ("parts"))).Click ();
// Via keyboard
Utils.Type ('p');
Adding Parts
// Add parts to job
string[] parts = [FXFILE, "InnerContour.fx"];
AddPartsOrLayouts (ELoadOption.Part, parts);
// Add single part
AddPartsOrLayouts (ELoadOption.Part, new[] { "123.fx" });
// External module reference
using static FluxTester.NestingUtils;
Parts Table Access
// Get parts table
var partsTable = job.FindFirstChild (a => a.ByClassName ("PartsTable")).FindFirstChild ();
// Get parts table with scrollviewer
var partsTable = Utils.FindElement (
() => job.FindFirstChild (a => a.ByClassName ("PartsTable")
.And (a.ByControlType (ControlType.Custom)))
).FindFirstChild ();
// Get parts from scrollviewer
var scrollViewer = Utils.FindElement (
() => job.FindFirstChild (a => a.ByClassName ("PartsTable"))
.FindFirstChild (a => a.ByClassName ("ScrollViewer"))
);
Part Table Interactions
// Get all parts in table
var parts = partsTable.FindAllChildren ();
// Find part by name
partsTable.FindFirstChild (a => a.ByName ("123"));
partsTable.FindFirstChild (a => a.ByName (partName.Replace (".fx", "")));
// Find part by index
var idx = Array.FindIndex (parts, a => a.Name = "BigCircle");
partsTable.FindChildAt (idx);
// Get specific column in row
partsTable.FindChildAt (idx + 3); // Priority column (idx + 3)
partsTable.FindChildAt (idx + 6); // Group column (idx + 6)
Part Operations
// Select part
var part = partsTable.FindFirstChild (a => a.ByName ("Name"));
part.Click ();
// Select all parts
Utils.Type ('a', CONTROL);
// Delete part
job.FindFirstChild ("cPartRemoveBtn").Click ();
// Delete all parts
foreach (var part in parts.Reverse ()) {
job.FindFirstChild ("cPartRemoveBtn").Click ();
}
// Save parts
job.FindFirstChild ("cSavePart").Click ();
Part Quantity Management
// Get quantity indices
var list = partsTable.FindAllChildren ();
var indices = list.Select ((el, index) => new { el, idx = index })
.Where (x => x.el.ClassName = "CheckBox")
.Select (x => x.idx)
.ToList ();
// Update quantity for each part
for (int i = 0; i < parts.Length + 1; i++) {
var pt = partsTable.FindChildAt (indices[i] - 5).GetClickablePoint ();
Utils.MouseClick (new System.Drawing.Point (pt.X - 40, pt.Y));
Thread.Sleep (500);
Utils.MouseClick (Mouse.Position, doubleClick: true);
Utils.Type ($"{(i + 1) * 10}"); // 10, 20, 30, etc.
}
Material Warning Handling
// Check for warning image
var warning = scrollViewer.FindFirstChild (a => a.ByClassName ("Image"));
if (warning != null) {
warning.Click ();
Utils.MainWin.FindFirstChild ()
.FindFirstChild (a => a.ByClassName ("ScrollViewer"))
.FindFirstChild (a => a.ByName ("Ignore and convert"))?.Click ();
}
// Alternative: Ignore and convert all
main.FindFirstDescendant (a => a.ByName ("Ignore and convert all"))?.Click ();
Part Validation
// Check part count in layout
CheckPartsCountInLayout (expectedPartsCount: 5);
CheckPartsCountInLayout (60, layoutIdx: 0);
// Helper
public static void CheckPartsCountInLayout (int expected, int layoutIdx = 0) {
Utils.MainWin.FindFirstChild ("NewJobWindow")
.FindFirstChild ("cPatternsList")
.FindChildAt (layoutIdx)
.FindFirstChild (a => a.ByClassName ("Image"))
.Click ();
Assert.AreEqual (
Utils.RunScriptWithOutput ("var result = Flux.Layout.Current.PartCount.ToString();"),
expected.ToString ()
);
}
Sheet Management
Sheets Tab Operations
// Switch to sheets tab
job.FindFirstChild (a => a.ByName ("sheets")).Click ();
// Access sheet buttons
var addBtn = job.FindFirstChild ("cAddSheet");
var delBtn = job.FindFirstChild ("cDelSheet");
var importBtn = job.FindFirstChild ("cImportSheet");
// Add sheets
for (int i = 0; i < 5; i++) addBtn.Click ();
// Delete sheets
for (int i = 0; i < 6; i++) delBtn.Click ();
Sheets Table Access
// Get sheets table
var pane = job.FindFirstChild (a => a.ByClassName ("SheetsTable")).FindFirstChild ();
// Find sheets by size
var sheets = pane.FindAllChildren (a => a.ByName ("3000x1500"));
// Count sheets
int sheetCount = sheets.Length;
// Verify sheet was added/removed
Assert.Equals (pane.FindAllChildren (a => a.ByName ("3000x1500")).Length, 6);
Sheet Import/Export
// Export remainder sheet
g.FindFirstChild (a => a.ByName ("Layout")).Click ();
g.FindFirstDescendant (a => a.ByName ("Export Remainder Sheets...")).Click ();
// Save exported file
g.FindFirstDescendant (a => a.ByClassName ("Button").And (a.ByName ("Save"))).Click ();
// Import sheet
job.FindFirstChild ("cImportSheet").Click ();
Thread.Sleep (1000);
// Type file path
Utils.Type ($"{Utils.tData}Test_C162420/test_ExportRemSheet.dxf".Replace ('/', '\\'));
// Click Open
Utils.MainWin.FindFirstChild ()
.FindFirstChild (a => a.ByClassName ("Button").And (a.ByName ("Open")))
.Click ();
Sheet Operations
// Create remainder sheet
SkeletonCut.CreateRemainderSheet ();
// Click on sheet
Utils.MouseClick (Utils.MeasurePtToPixel (new Utils.Measure (15.5, 15.5)));
// Get dialog
var g = Utils.WaitForGlob ();
// Delete sheet with confirmation
job.FindFirstChild ("cDelSheet").Click ();
Utils.MainWin.FindFirstChild ()
.FindFirstChild (a => a.ByClassName ("Button").And (a.ByName ("Yes")))?.Click ();
Part Properties Configuration
Priority Settings
// Set part priority
string[] parts = ["BigCircle", "Rectangle(400x200)", "Square(250x250)"];
List<string[]> priorityCombinations = [
["1", "2", "3"],
["2", "1", "3"],
["3", "2", "1"],
["0", "1", "2"]
];
// For each part, set priority
for (int k = 0; k < 3; k++) {
var idx = Array.FindIndex (partsTable.FindAllChildren (), a => a.Name = parts[k]);
partsTable.FindChildAt (idx + 3).DoubleClick (); // Priority column
Utils.MouseClick (Mouse.Position, doubleClick: true);
Utils.Type ($"{priorityCombinations[j][k]}"); // Enter priority
}
// Renest with new priorities
newJobWindow.FindFirstChild ("cNesting").Click ();
var layouts = Utils.FindElements (() => newJobWindow.FindFirstChild ("cPatternsList").FindAllChildren ());
// Validate each layout
for (int i = 0; i < layouts.Length; i++) {
layouts[i].Click ();
Nesting.CheckPartsInLayout ("C122180", $"test_SetPriority{j + 1}-{i + 1}");
}
Rotation Settings
// Set part rotation (6 options available)
for (int i = 0; i < 6; i++) {
newJobWindow.FindFirstChild (a => a.ByName ("parts")).Click ();
// Find and click rotation column
newJobWindow.FindFirstChild (a => a.ByClassName ("PartsTable"))
.FindFirstChild (a => a.ByClassName ("ScrollViewer"))
.FindAllChildren (a => a.ByClassName ("TextBlock"))[15]
.Click ();
// Select rotation option
newJobWindow.FindFirstDescendant (a => a.ByClassName ("ContextMenu"))
.AsMenu ()
.Items[i]
.AsMenuItem ()
.Click ();
// Switch to layout and validate
SwitchToLayoutPage ();
Nesting.CheckPartsInLayout ("C122181", $"test_SetRotation{i}");
}
Grouping Settings
// Set part group
string[] parts = ["BigCircle", "Rectangle(400x200)", "Square(250x250)"];
List<string[]> groupCombinations = [
["1", "2", "3"],
["2", "1", "3"],
["3", "2", "1"],
["0", "1", "2"]
];
// For each part, set group
for (int k = 0; k < 3; k++) {
var idx = Array.FindIndex (partsTable.FindAllChildren (), a => a.Name = parts[k]);
partsTable.FindChildAt (idx + 6).DoubleClick (); // Group column (idx + 6)
Utils.MouseClick (Mouse.Position, doubleClick: true);
Utils.Type ($"{groupCombinations[j][k]}"); // Enter group ID
}
// Renest and validate layouts
var layouts = Utils.FindElements (() => newJobWindow.FindFirstChild ("cPatternsList").FindAllChildren ());
for (int i = 0; i < layouts.Length; i++) {
layouts[i].Click ();
Nesting.CheckPartsInLayout ("C122182", $"test_LayoutGroup{j + 1}-{i + 1}");
}
Dialog Interactions
New Job Dialog
// Open new job dialog
Utils.MainWin.FindFirstChild ("CmdBar").FindFirstChild (a => a.ByName ("new job")).Click ();
// Get dialog
var dlg = Utils.MainWin.FindFirstChild (a => a.ByName ("NewJobDlg"));
// Type job name
if (dlg != null && string.IsNullOrEmpty (name)) Utils.Type (name);
// Click OK
dlg?.FindFirstChild (a => a.ByName ("OK").And (a.ByControlType (ControlType.Button))).Click ();
// Click Cancel
dlg.FindFirstChild (a => a.ByName ("Cancel").And (a.ByControlType (ControlType.Button))).Click ();
// Verify dialog appears
Assert.IsNotNull (dlg, "New job dialog is not found");
Save Parts Dialog
// Open save dialog
job.FindFirstChild ("cSavePart").Click ();
// Find dialog
Utils.FindElement (() => job.FindFirstChild (a => a.ByName ("Select Folder")));
// Type folder path
Utils.FindElement (() => job.FindFirstChild (a => a.ByName ("Select Folder")))
.FindFirstChild (a => a.ByControlType (ControlType.Edit).And (a.ByName ("Folder:")))
.Click ();
Utils.Type (Utils.tempPath.Replace ('/', '\\')[..^1]);
// Click Save
Utils.FindElement (() => Utils.MainWin.FindFirstDescendant (a => a.ByName ("Select Folder").And (a.ByClassName ("Button"))))
.Click ();
Confirmation Dialogs
// Click Yes button
Utils.MainWin.FindFirstChild ()
.FindFirstChild (a => a.ByClassName ("Button").And (a.ByName ("Yes")))
?.Click ();
// Click No button
Utils.MainWin.FindFirstChild ()
.FindFirstChild (a => a.ByClassName ("Button").And (a.ByName ("No")))
?.Click ();
// Material conversion warning
Utils.MainWin.FindFirstDescendant (a => a.ByName ("Ignore and convert all"))
?.Click ();
Save As Dialog
// Wait for Save As dialog
Utils.FindElement (() => Utils.MainWin.FindFirstDescendant (a => a.ByName ("Save As")));
// Click Save button
g.FindFirstDescendant (a => a.ByClassName ("Button").And (a.ByName ("Save")))
.Click ();
// Wait for file creation
Utils.WaitForFileExists (tempfile);
Add Dialog: Search & Sorting
Add Dialog Access
// Open Add dialog
Utils.Type ('a');
// Open Add dialog via helper
OpenAddDialog ("NestingJobSample.fxjob");
// Helper implementation
static void OpenAddDialog (string fileName) {
OpenJob (fileName);
Utils.Type (ESC);
Utils.Type ('a');
}
Search Operations
// Get search box
var searchBox = GetSearchBox ();
var searchBoxQuery = searchBox.FindFirstChild (a => a.ByClassName ("TextBox")).AsTextBox ();
// Search by part name
foreach (var uniquePartName in partNames.Distinct ()) {
searchBoxQuery.Text = uniquePartName;
var addPartNames = ExtractPartNames (GetParts ());
Assert.True (addPartNames.All (addPartName => addPartName = uniquePartName));
}
// Case-insensitive search
searchBoxQuery.Text = uniquePartName.ToUpper ();
var uppercaseResults = GetParts ();
searchBoxQuery.Text = uniquePartName.ToLower ();
var lowercaseResults = GetParts ();
Assert.AreEqual (ExtractPartNames (uppercaseResults), ExtractPartNames (lowercaseResults));
// Empty search (shows all)
searchBoxQuery.Text = "";
var addPartNames1 = ExtractPartNames (GetParts ());
// Invalid search
searchBoxQuery.Text = "ahsk%$$^%^&";
Assert.True (GetParts ().Length = 0);
// Placeholder text
searchBoxQuery.Text = "Search Parts";
var parts = GetParts ();
Sort Operations
// Get sort by combobox
var comboBox = GetComboBox ();
// Sort by Name
comboBox.Select ("Name");
SortAscendingly (true);
bool ascSort = IsSorted (ExtractPartNames (GetParts ()));
// Sort by Area
comboBox.Select ("Area");
var widths = GetMeasurements (false);
var heights = GetMeasurements ();
var areas = [.. widths.Select ((w, i) => w * heights[i])];
// Sort by Width
comboBox.Select ("Width");
var ascSort = IsSorted (GetMeasurements (false));
// Sort by Height
comboBox.Select ("Height");
var ascSort = IsSorted (GetMeasurements ());
// Sort by Remaining Quantity
comboBox.Select ("Remaining Quantity");
var ascSort = !IsSorted (GetRemQuantities ());
Sort Direction
// Toggle ascending/descending
SortAscendingly (true); // Ascending
SortAscendingly (false); // Descending
// Helper
static void SortAscendingly (bool b) {
GetSortToggleCheckBox ().IsToggled = b;
}
// Get toggle checkbox
var toggleCheckBox = Utils.MainWin.FindFirstDescendant (a => a.ByName ("Ascending")).AsCheckBox ();
Add Dialog Parts List
// Get all parts
var parts = GetParts ();
// Extract part names
string[] partNames = ExtractPartNames (parts);
// Get part measurements
float[] widths = GetMeasurements (false);
float[] heights = GetMeasurements ();
// Get remaining quantities
int[] remQuantities = GetRemQuantities ();
// Helper: Extract part names
static string[] ExtractPartNames (AutomationElement[] parts) =>
[.. parts.Select (a => a.Name.Split ('\n', 2).First ()[3..])];
// Helper: Get measurements
static float[] GetMeasurements (bool height = true) {
var parts = GetParts ();
int len = parts.Length;
var measurements = new float[len];
for (int i = 0; i < len; i++) {
var dims = parts[i].Name.Split ('\n', 3)[1].Split ('x');
measurements[i] = float.Parse (height ? dims[1] : dims[0]);
}
return measurements;
}
// Helper: Get remaining quantities
static int[] GetRemQuantities () {
var parts = GetParts ();
int len = parts.Length;
int[] remQuantities = new int[len];
for (int i = 0; i < parts.Length; i++) {
var line3 = parts[i].Name.Split ('\n', 4)[2];
int idx = line3.IndexOfAny (['-', '+']);
remQuantities[i] = idx >= 0 ? int.Parse (line3[idx..]) : 0;
}
return remQuantities;
}
Add Dialog UI Validation
// Check search box exists
Assert.IsNotNull (GetSearchBox (), "Search Box not found");
// Check sort option exists
Assert.IsNotNull (mainWin.FindFirstDescendant (a => a.ByName ("Ascending")), "Sort option not found");
// Check sort options available
var comboBoxItems = GetComboBox ().Items;
foreach (var item in comboBoxItems) {
Assert.IsNotNull (item, item.Name + " element not found");
}
Keyboard & Input Commands
Navigation Keys
// Tab navigation
Utils.Type ('p'); // Parts tab
Utils.Type ('l'); // Layouts tab
Utils.Type ('a'); // Add dialog
// Escape/Close
Utils.Type (ESC);
Utils.Type (ESCAPE);
// Confirm
Utils.Type (ENTER);
// Movement
Utils.Type (LEFT);
Utils.Type (RIGHT);
Utils.Type (DELETE);
Modifier Keys
using static FluxTester.Utils.KeyShort;
// Ctrl combinations
Utils.Type ('a', CONTROL); // Select all
Utils.Type ('c', CONTROL); // Copy
Utils.Type ('x', CONTROL); // Cut
Utils.Type ('v', CONTROL); // Paste
// Key constants
CONTROL
SHIFT
ESC
ESCAPE
ENTER
TAB
LEFT
RIGHT
DELETE
Text Input
// Type job name
Utils.Type (name);
// Type priority value
Utils.Type ($"{priority}");
// Type quantity
Utils.Type ($"{(i + 1) * 10}");
// Type file path
Utils.Type ($"{Utils.tData}Test_C162420/test_ExportRemSheet.dxf".Replace ('/', '\\'));
// Type folder path
Utils.Type (Utils.tempPath.Replace ('/', '\\')[..^1]);
Mouse Operations
Basic Mouse Actions
// Single click
Utils.MouseClick (element.GetClickablePoint ());
// Double click
Utils.MouseClick (pt, doubleClick: true);
// Click with modifier
Utils.MouseClick (pt, CONTROL); // Ctrl+click
// Get current position
var currentPos = Mouse.Position;
Point Conversion
// Convert measurement to pixels
var pt = Utils.MeasurePtToPixel (new Utils.Measure (15.5, 15.5));
var pt = Utils.MeasurePtToPixel (new Utils.Measure (50, 90));
// Adjust point coordinates
new System.Drawing.Point (pt.X - 40, pt.Y);
Drag & Drop
// Standard drag and drop
Utils.DragAndDrop (startPoint, endPoint);
// Copy (Ctrl+drag)
using (var ob = Keyboard.Pressing ((VirtualKeyShort)CONTROL))
Utils.DragAndDrop (start, end);
// Twinline (Shift+drag)
using (var ob = Keyboard.Pressing ((VirtualKeyShort)SHIFT))
Utils.DragAndDrop (start, end);
UI Element Access
Job Window Navigation
// Get main window
var mainWin = Utils.MainWin;
// Get job window
var job = Utils.MainWin.FindFirstChild ("NewJobWindow");
// Get job window with retry
var newJobWindow = GetNewJobWindow ();
// Helper
static AutomationElement GetNewJobWindow () {
AutomationElement newJobWindow;
do newJobWindow = Utils.MainWin.FindFirstChild ("NewJobWindow");
while (newJobWindow.FindAllChildren ().Length = 0);
return newJobWindow;
}
Tab Controls
// Find and click tabs
job.FindFirstChild (a => a.ByName ("parts")).Click ();
job.FindFirstChild (a => a.ByName ("layouts")).Click ();
job.FindFirstChild (a => a.ByName ("sheets")).Click ();
// Verify tab exists
Utils.FindElement (() => job.FindFirstChild (a => a.ByName ("parts")));
Button Controls
// Find buttons by automation ID
var addBtn = job.FindFirstChild ("cAddLayout");
var removeBtn = job.FindFirstChild ("cRemovePattern");
var nestBtn = job.FindFirstChild ("cNesting");
var savePartBtn = job.FindFirstChild ("cSavePart");
var importSheetBtn = job.FindFirstChild ("cImportSheet");
// Find buttons by name
Utils.MainWin.FindFirstChild ("CmdBar").FindFirstChild (a => a.ByName ("new job"));
// Click button
button.Click ();
Table Controls
// Get parts table
var partsTable = job.FindFirstChild (a => a.ByClassName ("PartsTable")).FindFirstChild ();
// Get sheets table
var sheetsTable = job.FindFirstChild (a => a.ByClassName ("SheetsTable")).FindFirstChild ();
// Get patterns list
var patterns = job.FindFirstChild ("cPatternsList");
// Navigate table
partsTable.FindChildAt (index);
patterns.FindChildAt (0);
patterns.FindAllChildren ();
ComboBox Controls
// Get sort by combobox
var comboBox = Utils.FindElement (
() => Utils.MainWin.FindFirstDescendant (a => a.ByControlType (ControlType.ComboBox))
).AsComboBox ();
// Select option
comboBox.Select ("Name");
comboBox.Select ("Area");
comboBox.Select ("Width");
comboBox.Select ("Height");
comboBox.Select ("Remaining Quantity");
// Expand/collapse
comboBox.Expand ();
comboBox.Items[index].Click ();
// Get items
var items = comboBox.Items;
CheckBox Controls
// Get sort toggle
var toggleCheckBox = Utils.MainWin.FindFirstDescendant (a => a.ByName ("Ascending")).AsCheckBox ();
// Toggle state
toggleCheckBox.IsToggled = true;
toggleCheckBox.IsToggled = false;
TextBox Controls
// Get textbox
var searchBox = GetSearchBox ();
var textBox = searchBox.FindFirstChild (a => a.ByClassName ("TextBox")).AsTextBox ();
// Set text
textBox.Text = "searchValue";
// Get text
string value = textBox.Text;
Glob/Dialog Controls
// Wait for glob
var g = Utils.WaitForGlob ();
// Find in glob
g.FindFirstChild (a => a.ByName ("Layout"));
g.FindFirstDescendant (a => a.ByName ("Export Remainder Sheets..."));
// Get context menu
var menu = newJobWindow.FindFirstDescendant (a => a.ByClassName ("ContextMenu")).AsMenu ();
menu.Items[i].AsMenuItem ().Click ();
Data Extraction & Validation
Extracting Job Data
// Get part names from job
List<string> commands = [
"StringBuilder sb = new ();",
"foreach (var part in Flux.JobEditor.Current.Job.Parts) sb.Append(part.Name + \",\");",
"var result = sb.ToString();"
];
string[] partNames = Utils.RunScriptWithOutput (commands).Split (',', options: StringSplitOptions.RemoveEmptyEntries);
Part Name Extraction
// Extract from Add dialog
string[] ExtractPartNames (AutomationElement[] parts) =>
[.. parts.Select (a => a.Name.Split ('\n', 2).First ()[3..])];
// Usage
var addPartNames = ExtractPartNames (GetParts ());
Sorting Validation
// Check if sorted ascending
IsSorted (array);
IsSorted (array, ascending: true);
// Check if sorted descending
IsSorted (array, ascending: false);
// Generic implementation
static bool IsSorted<T> (T[] array, bool ascending = true) where T : IComparable<T> =>
array.SequenceEqual (ascending ? array.OrderBy (x => x) : array.OrderByDescending (x => x));
// Usage
bool namesSorted = IsSorted (ExtractPartNames (GetParts ()));
bool areasSorted = IsSorted (GetAreas ());
Layout Data Dumping
// Dump to file
Utils.DumpAutomationElement (
jobWin.FindFirstChild ("cPatternsList").FindFirstChild ()
);
// Verify with reference
Assert.AreTextFilesEqual (
Utils.tData + "Test_C150733/test.txt",
Utils.tempPath + "temp.txt"
);
File Validation
// Check file exists
Utils.WaitForFileExists (file);
Assert.True (File.Exists (file));
// Compare text files
Assert.AreTextFilesEqual (expectedPath, actualPath);
// Compare binary files (DXF)
Assert.AreTextFilesEqual (
$"{Utils.tData}Test_C162420/test_ExportRemSheet.dxf",
tempfile
);
Script Execution
Job Data Queries
// Get all parts in job
List<string> commands = [
"StringBuilder sb = new ();",
"foreach (var part in Flux.JobEditor.Current.Job.Parts) sb.Append(part.Name + \",\");",
"var result = sb.ToString();"
];
string result = Utils.RunScriptWithOutput (commands);
Layout Validation
// Get part count in current layout
Utils.RunScriptWithOutput ("var result = Flux.Layout.Current.PartCount.ToString();");
// Validate parts in layout
CheckPartsCountInLayout (expectedCount);
CheckPartsCountInLayout (5, layoutIdx: 0);
Layout Properties
// Check if all parts within margin
Utils.RunScriptWithOutput ("var result = Flux.Layout.Current.Children.All (...).ToString();");
// Access layout children
Nesting.AreAllPartsNestedWithinSheet (includeTooling: true);
Multi-Line Script
List<string> commands = [
"var lay = Flux.Layout.Current;",
"var sm = lay.SheetMargin;",
"var size = lay.SheetSize;",
"var bound = new Flux.Bound2 (sm.Left - 0.1, sm.Bottom - 0.1, size.X - sm.Right + 0.1, size.Y - sm.Top + 0.1);",
"var result = lay.Children.All (a => bound.Contains (a.GetBoundSansTooling ())).ToString();"
];
Common Job Workflows
Basic Job Creation Workflow
[Test (1528, "Selecting 'New Job' toolbar button opens a pop-up dialog")]
public void Test_C122130 () {
// 1. Open Flux
Utils.OpenFlux ();
// 2. Open New Job dialog
Utils.MainWin.FindFirstChild ("CmdBar").FindFirstChild (a => a.ByName ("new job")).Click ();
// 3. Verify dialog appears
Assert.IsNotNull (Utils.MainWin.FindFirstChild (a => a.ByName ("NewJobDlg")),
"New job dialog is not found");
}
Job with Parts Addition Workflow
[Test (1530, "Save Parts(s) from the job")]
public void Test_C200408 () {
// 1. Open Flux and create job
Utils.OpenFlux ();
var job = CreateNewJob ();
// 2. Add parts
string[] parts = [FXFILE, "InnerContour.fx"];
AddPartsOrLayouts (ELoadOption.Part, parts);
// 3. Switch to parts tab
Utils.FindElement (() => job.FindFirstChild (a => a.ByName ("parts"))).Click ();
// 4. Select all and save
job.FindFirstChild (a => a.ByClassName ("PartsTable")).FindFirstChild ()
.FindFirstChild (a => a.ByName ("Name")).Click ();
Utils.Type ('a', CONTROL);
job.FindFirstChild ("cSavePart").Click ();
// 5. Specify save location
Utils.FindElement (() => job.FindFirstChild (a => a.ByName ("Select Folder")))
.FindFirstChild (a => a.ByControlType (ControlType.Edit).And (a.ByName ("Folder:")))
.Click ();
Utils.Type (Utils.tempPath.Replace ('/', '\\')[..^1]);
// 6. Save and verify
Utils.FindElement (() => Utils.MainWin.FindFirstDescendant (a => a.ByName ("Select Folder")
.And (a.ByClassName ("Button")))).Click ();
foreach (var filename in parts) {
var file = Utils.tempPath + filename;
Utils.WaitForFileExists (file);
Assert.True (File.Exists (file));
Thread.Sleep (500);
}
}
Layout Management Workflow
[Test (401, "Adding and deleting layouts")]
public void Test_C150732 () {
// 1. Open job
Utils.OpenJobOrLayout (Utils.tData + "TwinJob.fxjob");
// 2. Get job window and switch to layouts
var jobWin = Utils.MainWin.FindFirstDescendant (a => a.ByName ("parts")).Parent;
jobWin.FindFirstChild (a => a.ByName ("layouts")).Click ();
// 3. Get buttons
var addBtn = jobWin.FindFirstChild ("cAddLayout");
var removeBtn = jobWin.FindFirstChild ("cRemovePattern");
// 4. Add layouts
Assert.True (layoutsCount () = 1);
for (int i = 0; i < 6; i++) addBtn.Click ();
Assert.True (layoutsCount () = 7);
// 5. Remove layouts
for (int i = 0; i < 3; i++) removeBtn.Click ();
Assert.True (layoutsCount () = 4);
// 6. Remove middle layout
jobWin.FindFirstChild ("cPatternsList").FindChildAt (new Random ().Next (3)).Click ();
removeBtn.Click ();
Assert.True (layoutsCount () = 3);
// Helper
int layoutsCount () => jobWin.FindFirstChild ("cPatternsList").FindAllChildren ().Length;
}
Priority Setting Workflow
[Test (3527, "Set the priority for the part")]
public void Test_C122180 () {
// 1. Open job
Utils.OpenJobOrLayout (Utils.tData + "NestingJobSample.fxjob");
// 2. Test multiple priority combinations
string[] parts = ["BigCircle", "Rectangle(400x200)", "Square(250x250)"];
List<string[]> priorityCombinations = [["1", "2", "3"], ["2", "1", "3"], ["3", "2", "1"], ["0", "1", "2"]];
for (int j = 0; j < priorityCombinations.Count; j++) {
var newJobWindow = GetNewJobWindow ();
newJobWindow.FindFirstChild (a => a.ByName ("parts")).Click ();
// 3. Get parts table
var partsTable = Utils.FindElement (() => newJobWindow
.FindFirstChild (a => a.ByClassName ("PartsTable")
.And (a.ByControlType (ControlType.Custom)))).FindFirstChild ();
// 4. Set priority for each part
for (int k = 0; k < 3; k++) {
var idx = Array.FindIndex (partsTable.FindAllChildren (), a => a.Name = parts[k]);
partsTable.FindChildAt (idx + 3).DoubleClick ();
Utils.MouseClick (Mouse.Position, doubleClick: true);
Utils.Type ($"{priorityCombinations[j][k]}");
}
// 5. Switch to layout and renest
SwitchToLayoutPage ();
Utils.FindElement (() => newJobWindow.FindFirstChild ("cPatternsList").FindFirstChild ()).Click ();
Utils.Type ("a", CONTROL);
Utils.Type (DELETE);
newJobWindow.FindFirstChild ("cNesting").Click ();
// 6. Validate layouts
var layouts = Utils.FindElements (() => newJobWindow.FindFirstChild ("cPatternsList").FindAllChildren ());
for (int i = 0; i < layouts.Length; i++) {
layouts[i].Click ();
Nesting.CheckPartsInLayout ("C122180", $"test_SetPriority{j + 1}-{i + 1}");
}
}
}
Search & Sort Workflow
[Test (3531, "Search the parts by its name in add button")]
public void Test_C200439 () {
// 1. Open job
OpenJob ("NestingJobSample.fxjob");
// 2. Get job window and parts
var mainWin = Utils.MainWin;
var newJobWindow = mainWin.FindFirstChild ("NewJobWindow");
newJobWindow.FindFirstChild (a => a.ByName ("parts")).Click ();
// 3. Get all part names
List<string> commands = [
"StringBuilder sb = new ();",
"foreach (var part in Flux.JobEditor.Current.Job.Parts) sb.Append(part.Name + \",\");",
"var result = sb.ToString();"
];
string[] partNames = Utils.RunScriptWithOutput (commands)
.Split (',', options: StringSplitOptions.RemoveEmptyEntries);
// 4. Open Add dialog
Utils.Type (ESC);
Utils.Type ('a');
var searchBoxQuery = GetSearchBox ()
.FindFirstChild (a => a.ByClassName ("TextBox")).AsTextBox ();
// 5. Search for each unique part
foreach (var uniquePartName in partNames.Distinct ()) {
searchBoxQuery.Text = uniquePartName;
var addPartNames = ExtractPartNames (GetParts ());
Assert.True (addPartNames.All (addPartName => addPartName = uniquePartName));
}
// 6. Test case-insensitive search
foreach (var uniquePartName in partNames.Distinct ()) {
searchBoxQuery.Text = uniquePartName.ToUpper ();
var uppercaseResults = GetParts ();
searchBoxQuery.Text = uniquePartName.ToLower ();
var lowercaseResults = GetParts ();
Assert.AreEqual (ExtractPartNames (uppercaseResults), ExtractPartNames (lowercaseResults));
}
// 7. Test empty search and invalid search
searchBoxQuery.Text = "";
var addPartNames1 = ExtractPartNames (GetParts ());
Assert.True (partNames.All (partName => addPartNames1.Contains (partName)));
searchBoxQuery.Text = "ahsk%$$^%^&";
Assert.True (GetParts ().Length = 0);
}
Copy/Paste Workflow
[Test (3530, "Cut/Copy & Paste parts in layout sheets")]
public void C145731 () {
// 1. Open job
Utils.OpenJobOrLayout (Utils.tData + "AsymmetricalPolygon.fxjob");
// 2. Handle material warning if needed
var newJobWindow = Utils.FindElement (() => Utils.MainWin.FindFirstChild ("NewJobWindow"));
newJobWindow.FindFirstChild (a => a.ByName ("parts")).Click ();
var scrollViewer = newJobWindow.FindFirstChild (a => a.ByClassName ("PartsTable"))
.FindFirstChild (a => a.ByClassName ("ScrollViewer"));
var warning = scrollViewer.FindFirstChild (a => a.ByClassName ("Image"));
if (warning != null) {
warning.Click ();
Utils.MainWin.FindFirstChild ()
.FindFirstChild (a => a.ByClassName ("ScrollViewer"))
.FindFirstChild (a => a.ByName ("Ignore and convert"))?.Click ();
}
// 3. Test Copy/Paste
SwitchToLayoutPage ();
Thread.Sleep (2000);
Utils.Type (ESC);
var pt = Utils.MeasurePtToPixel (new Utils.Measure (50, 90));
Utils.MouseClick (pt);
Utils.Type ("c", CONTROL); // Copy
Utils.Type ("v", CONTROL); // Paste
Assert.True (Nesting.AreAllPartsNestedWithinSheet ());
Nesting.CheckPartsInLayout ("C145731", "test_CopyPaste");
// 4. Test Cut/Paste
SwitchToLayoutPage ();
newJobWindow.FindFirstChild ("cRemovePattern").Click ();
newJobWindow.FindFirstChild ("cNesting").Click ();
Thread.Sleep (2000);
Utils.Type (ESC);
Thread.Sleep (500);
Utils.MouseClick (pt);
Utils.Type ("x", CONTROL); // Cut
Utils.Type ("v", CONTROL); // Paste
Assert.True (Nesting.AreAllPartsNestedWithinSheet ());
Nesting.CheckPartsInLayout ("C145731", "test_CutPaste");
}
Helper Methods Reference
Job Operations
CreateNewJob(string name = "") // Create new job
OpenJob(string fileName) // Open job from test data
GetNewJobWindow() // Get job window with retry
OpenAddDialog(string fileName) // Open job and Add dialog
Layout Operations
SwitchToLayoutPage() // Navigate to layout tab
WaitTillNestingCompletes(int waitTime = 6) // Wait for nesting
CheckPartsCountInLayout(int expected, int layoutIdx = 0) // Validate part count
Search & Sort
GetComboBox() // Get Sort By dropdown
GetSearchBox() // Get search textbox
GetSortToggleCheckBox() // Get sort direction toggle
GetParts() // Get all parts in Add dialog
GetNewJobWindow() // Get job window after loading
Data Extraction
ExtractPartNames(AutomationElement[] parts) // Extract names from parts
GetMeasurements(bool height = true) // Extract width/height values
GetRemQuantities() // Extract remaining quantities
GetAreas() // Calculate areas (width × height)
Sorting & Comparison
IsSorted<T>(T[] array, bool ascending = true) // Check sort order
SortAscendingly(bool b) // Toggle sort direction
Best Practices
✅ DO: - Use GetNewJobWindow() for consistent job window access with
retry logic - Always call Utils.Type (ESC) to finalize operations and
close dialogs - Use IDisposable pattern for comprehensive cleanup -
Use measurement points for consistent coordinate conversion - Validate
with both file comparison AND part count checking - Use meaningful
variable names for table indices and part references - Add comments for
non-obvious UI automation steps - Use helper methods for repeated
patterns (search, sort, priority) - Wait appropriately for async
operations (nesting, file operations)
❌ DON’T: - Hardcode pixel coordinates (always use measurement point
conversion) - Skip Utils.WaitForFileExists() when checking file
creation - Mix view switches without proper verification - Create
multiple jobs without adequate cleanup - Skip assertions in validation
workflows - Forget to handle material conversion warnings - Use generic
variable names (g, dlg, etc. without context) - Access table children
without understanding the index layout - Skip timeout specifications in
FindElement() calls
Common Pitfalls & Solutions
Job Window Not Loading
// Problem: FindAllChildren() returns 0
// Solution: Use GetNewJobWindow() helper
var newJobWindow = GetNewJobWindow ();
// Helper waits until all children load
static AutomationElement GetNewJobWindow () {
AutomationElement newJobWindow;
do newJobWindow = Utils.MainWin.FindFirstChild ("NewJobWindow");
while (newJobWindow.FindAllChildren ().Length = 0);
return newJobWindow;
}
Parts Table Column Access
// Problem: Don't know which column index to use
// Solution: Use specific offsets:
// idx + 3 = Priority column
// idx + 6 = Group column
var idx = Array.FindIndex (partsTable.FindAllChildren (), a => a.Name = partName);
partsTable.FindChildAt (idx + 3).DoubleClick (); // Priority
partsTable.FindChildAt (idx + 6).DoubleClick (); // Group
Material Conversion Warning
// Problem: Dialog blocks further operations
// Solution: Check and click warning
var warning = scrollViewer.FindFirstChild (a => a.ByClassName ("Image"));
if (warning != null) {
warning.Click ();
Utils.MainWin.FindFirstChild ()
.FindFirstChild (a => a.ByClassName ("ScrollViewer"))
.FindFirstChild (a => a.ByName ("Ignore and convert"))?.Click ();
}
File Save Operations
// Problem: File not found immediately after save
// Solution: Use wait and sleep
Utils.WaitForFileExists (tempfile);
Thread.Sleep (500); // Additional buffer
Assert.True (File.Exists (file));
Add Dialog Not Appearing
// Problem: Add dialog doesn't open
// Solution: Escape first, then open
Utils.Type (ESC);
Utils.Type ('a');
// Or use helper
OpenAddDialog ("NestingJobSample.fxjob");
Quick Reference: Operation Matrix
| Operation | Command | Example |
|---|---|---|
Open Job |
|
|
Create Job |
|
|
Get Job Window |
|
|
Add Part |
|
|
Set Priority |
|
Set via textbox entry |
Set Group |
|
Set via textbox entry |
Add Layout |
|
Loop for multiple |
Renest |
|
Wait with
|
Search Parts |
|
|
Sort Parts |
|
Multiple sort options available |
Validate Count |
|
|
Save Parts |
|
Specify save folder |
Export Sheet |
|
DXF format |
Import Sheet |
|
Choose DXF file |
This recipe book covers all major patterns and commands used in the Job.cs framework for creating automated job management and nesting tests in the Flux application.