Skip to content
Open
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
- name: Install ESP-IDF via eim
uses: espressif/install-esp-idf-action@v1
with:
version: 'v5.4'
version: 'v6.0'

- name: Set up Maven
uses: stCarolas/setup-maven@v5
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,115 +30,170 @@

/**
* Test class to test the Flash process
*
*
* @author Andrii Filippov
*
*/
@SuppressWarnings("restriction")
@RunWith(SWTBotJunit4ClassRunner.class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)

public class NewEspressifIDFProjectFlashProcessTest
{
public class NewEspressifIDFProjectFlashProcessTest {
@BeforeClass
public static void beforeTestClass() throws Exception
{
public static void beforeTestClass() throws Exception {
Fixture.loadEnv();
}

@AfterClass
public static void tearDown()
{
public static void tearDown() {
Fixture.cleanupEnvironment();
}

@Test
public void givenNewProjectCreatedBuiltWhenSelectSerialPortWhenFlashThenCheckFlashedSuccessfully() throws Exception
{
public void givenNewProjectCreatedBuiltWhenSelectSerialPortWhenFlashThenCheckFlashedSuccessfully()
throws Exception {
if (SystemUtils.IS_OS_LINUX) // temporary solution until new ESP boards arrive for Windows
{
Fixture.givenNewEspressifIDFProjectIsSelected("EspressIf", "Espressif IDF Project");
Fixture.givenProjectNameIs("NewProjectFlashTest");
Fixture.whenNewProjectIsSelected();
Fixture.whenTurnOffOpenSerialMonitorAfterFlashingInLaunchConfig();
Fixture.whenProjectIsBuiltUsingContextMenu();
Fixture.whenSelectLaunchTargetSerialPort();
Fixture.whenFlashProject();
Fixture.thenVerifyFlashDoneSuccessfully();
}
else
{
Fixture.whenBuildAndFlashForAllTargetsSequentially();
Fixture.whenChangeLaunchTargetBackToESP32();
} else {
assertTrue(true);
}
}

private static class Fixture
{
private static class Fixture {
private static SWTWorkbenchBot bot;
private static String category;
private static String subCategory;
private static String projectName;

private static void loadEnv() throws Exception
{
private static final TargetPort[] TARGETS = new TargetPort[] {
new TargetPort("esp32", "/dev/ttyUSB1 Dual RS232-HS"),
new TargetPort("esp32c61", "/dev/ttyUSB0 CP2102N USB to UART Bridge Controller"),
new TargetPort("esp32c5", "/dev/ttyUSB2 CP2102N USB to UART Bridge Controller"),
new TargetPort("esp32h2", "/dev/ttyUSB3 CP2102N USB to UART Bridge Controller"),
new TargetPort("esp32h4", "/dev/ttyUSB4 CP2102N USB to UART Bridge Controller"),
new TargetPort("esp32s2", "/dev/ttyUSB5 CP2102N USB to UART Bridge Controller"),
new TargetPort("esp32s3", "/dev/ttyUSB6 CP2102N USB to UART Bridge Controller"),
new TargetPort("esp32c3", "/dev/ttyUSB7 CP2102N USB to UART Bridge Controller")
};
Comment on lines +73 to +81
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Hardcoded ports/descriptors make this test brittle and environment-specific.

The TARGETS array pins each target to an exact /dev/ttyUSBN device path and a specific USB-UART chip description (e.g. Dual RS232-HS, CP2102N USB to UART Bridge Controller). This assumes:

  • A fixed enumeration order of USB devices on the CI runner (kernel assigns ttyUSBN in the order devices are probed, which is not stable across reboots, replugging, or hub changes).
  • A specific collection of adapters is always attached (different boards ship with different USB bridges — e.g. some ESP32-S3/C5 dev kits use native USB, not CP2102N).

If any device re-enumerates, the test will silently pick the wrong board and flash it, or fall back to the startsWith match on line 164 and still flash the wrong target. At minimum, consider:

  • Reading the target/port map from a configuration file or environment variable so the CI maintainer can adjust without a code change.
  • Using udev symlinks (e.g. /dev/esp32s3) pinned by USB serial number rather than ttyUSBN.
  • Documenting the exact runner hardware layout in a README alongside this test.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@tests/com.espressif.idf.ui.test/src/com/espressif/idf/ui/test/executable/cases/project/NewEspressifIDFProjectFlashProcessTest.java`
around lines 73 - 82, The TARGETS array in
NewEspressifIDFProjectFlashProcessTest is brittle due to hardcoded /dev/ttyUSBN
paths and chip descriptions; change it to load target-to-port mappings from a
configurable source (environment variable, system property, or external config
file/JSON) instead of the static TARGETS initializer, and update any code that
references TARGETS (e.g., tests that call into the flash selection logic) to
read that configuration at test startup; alternatively, support resolving ports
via stable identifiers (udev symlink names or serial numbers) by adding a
resolver that maps logical target names to /dev entries, and document expected
CI configuration in the test README.


private static void loadEnv() throws Exception {
bot = WorkBenchSWTBot.getBot();
EnvSetupOperations.setupEspressifEnv(bot);
bot.sleep(1000);
ProjectTestOperations.deleteAllProjects(bot);
}

private static void givenNewEspressifIDFProjectIsSelected(String category, String subCategory)
{
private static void givenNewEspressifIDFProjectIsSelected(String category, String subCategory) {
Fixture.category = category;
Fixture.subCategory = subCategory;
}

private static void givenProjectNameIs(String projectName)
{
private static void givenProjectNameIs(String projectName) {
Fixture.projectName = projectName;
}

private static void whenNewProjectIsSelected() throws Exception
{
private static void whenNewProjectIsSelected() throws Exception {
ProjectTestOperations.setupProject(projectName, category, subCategory, bot);
}

private static void whenProjectIsBuiltUsingContextMenu() throws IOException
{
private static void whenBuildAndFlashForAllTargetsSequentially() throws Exception {
for (int i = 0; i < TARGETS.length; i++) {
TargetPort tp = TARGETS[i];

boolean skipTargetChangeDialog = (i == 0);
whenChangeLaunchTarget(tp.target, skipTargetChangeDialog);

whenProjectIsBuiltUsingContextMenu();
whenSelectLaunchTargetSerialPort(tp.port);
whenFlashProject();
thenVerifyFlashDoneSuccessfully();

TestWidgetWaitUtility.waitForOperationsInProgressToFinishAsync(bot);
bot.sleep(500);
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

private static void whenChangeLaunchTarget(String targetText, boolean skipDialogOnThisRun) throws Exception {
// skip first iteration since esp32 target was selected during Project Creation
if (!skipDialogOnThisRun) {
LaunchBarTargetSelector targetSelector = new LaunchBarTargetSelector(bot);
targetSelector.selectTarget(targetText);
TestWidgetWaitUtility.waitForDialogToAppear(bot, "IDF Launch Target Changed", 20000);
SWTBotShell shell = bot.shell("IDF Launch Target Changed");
shell.setFocus();
bot.button("Yes").click();
}

TestWidgetWaitUtility.waitForOperationsInProgressToFinishAsync(bot);
}

private static void whenChangeLaunchTargetBackToESP32() throws Exception {
LaunchBarTargetSelector targetSelector = new LaunchBarTargetSelector(bot);
targetSelector.selectTarget("esp32");
TestWidgetWaitUtility.waitForDialogToAppear(bot, "IDF Launch Target Changed", 20000);
SWTBotShell shell = bot.shell("IDF Launch Target Changed");
shell.setFocus();
bot.button("Yes").click();
TestWidgetWaitUtility.waitForOperationsInProgressToFinishAsync(bot);
}

private static void whenProjectIsBuiltUsingContextMenu() throws IOException {
ProjectTestOperations.buildProjectUsingContextMenu(projectName, bot);
ProjectTestOperations.waitForProjectBuild(bot);
TestWidgetWaitUtility.waitForOperationsInProgressToFinishAsync(bot);
}

private static void whenSelectLaunchTargetSerialPort() throws Exception
{
private static void whenSelectLaunchTargetSerialPort(String portPrefixOrExact) throws Exception {
LaunchBarTargetSelector targetSelector = new LaunchBarTargetSelector(bot);
targetSelector.clickEdit();

TestWidgetWaitUtility.waitForDialogToAppear(bot, "New ESP Target", 20000);
SWTBotShell shell = bot.shell("New ESP Target");
bot.comboBoxWithLabel("Serial Port:").setSelection("/dev/ttyUSB1 Dual RS232-HS");
try {
bot.comboBoxWithLabel("Serial Port:").setSelection(portPrefixOrExact);
} catch (Exception ignored) {
String[] items = bot.comboBoxWithLabel("Serial Port:").items();
String match = null;

for (String item : items) {
if (item != null && item.startsWith(portPrefixOrExact)) {
match = item;
break;
}
}

if (match == null) {
throw new AssertionError("No serial port matched: " + portPrefixOrExact + " ; available="
+ String.join(", ", items));
}

bot.comboBoxWithLabel("Serial Port:").setSelection(match);
}

Comment on lines +150 to +176
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Identify which exception type setSelection throws when the item is absent,
# to help narrow the catch clause.
rg -nP 'setSelection\s*\(' -C2 --type=java | head -80
rg -nP 'class\s+SWTBotCombo\b' -C3

Repository: espressif/idf-eclipse-plugin

Length of output: 11266


🌐 Web query:

SWTBot setSelection exception WidgetNotFoundException IllegalArgumentException

💡 Result:

The exceptions WidgetNotFoundException and IllegalArgumentException when using SWTBot's setSelection (likely on SWTBotTree or SWTBotTable) typically occur due to timing, focus, or incorrect widget targeting issues. WidgetNotFoundException causes: - Widget not yet loaded or disposed: Common in trees when trying to access child nodes without expanding parent first (e.g., bot.tree.getTreeItem("Parent").getNode("Child").select fails; expand first: item.expand). - Wrong widget index: Multiple trees/tables present; use bot.tree(1) or bot.table(0) instead of bot.tree. - Timing/race conditions: Widget appears/disappears intermittently. Increase timeouts with -Dorg.eclipse.swtbot.playback.poll.delay=500 -Dorg.eclipse.swtbot.playback.delay=400, or use bot.sleep / waitUntil conditions. - Focus loss: Especially in debugger or headless (Xvfb); force shell activation: bot.activeShell.setFocus or UIThreadRunnable.syncExec to forceActive. - Linux-specific focus issues: Use Xephyr for tests. IllegalArgumentException causes: - Invalid index or argument to setSelection (e.g., out-of-bounds row/column index). - Underlying SWT widget state invalid (e.g., disposed, wrong thread access). - For trees: Path not fully expanded before selection. - Launch config issues: Missing SWTBot plugins or classloader problems in Eclipse tests. Solutions: 1. Ensure proper expansion: SWTBotTreeItem item = bot.tree.expandNode("Parent", "Child"); item.select; 2. Wait for widgets: bot.waitUntil(Conditions.shellIsActive("Title")); bot.waitUntil(new DefaultCondition { test { return bot.tree.getItems.length > 0; } }); 3. Use correct indices: Inspect with Eclipse Spy or print bot.widgets(WidgetOfType.widgetOfType(Tree.class)). 4. Activate contexts: bot.tabItem("Tab").activate; bot.tree(1).select(0); 5. JVM args for timing: As above. 6. For tables: Ensure column count matches; bot.table.select(rowIndex); Official docs confirm setSelection throws WidgetNotFoundException if widget null/disposed, and operations fail if not enabled/found.

Citations:


🌐 Web query:

SWTBotCombo setSelection API documentation Eclipse SWTBot

💡 Result:

The SWTBotCombo class in Eclipse SWTBot provides the setSelection(String text) method to set the combo box selection to the specified text. public void setSelection(String text) Set the selection to the specified text. Parameters: text - the text to set into the combo. This is documented in the official SWTBot API documentation. Usage example from tests and tutorials: SWTBotCombo combo = bot.comboBox; // or bot.comboBoxWithLabel("Label"); combo.setSelection("Option Text"); There is also typically a setSelection(int index) method inherited or available for index-based selection, and setText(String text) for editable combos, as seen in recorder tests: bot.comboBox.setText("value"); The API is stable across versions, with the primary method being setSelection(String text) for text matching.

Citations:


🌐 Web query:

site:git.eclipse.org SWTBotCombo setSelection source code

💡 Result:

The source code for SWTBotCombo.setSelection in Eclipse SWTBot is located in the official GitHub repository at https://github.com/eclipse-swtbot/org.eclipse.swtbot. SWTBotCombo is a widget class in org.eclipse.swtbot.swt.finder.widgets package that wraps SWT Combo or CCombo controls. The setSelection method typically delegates to the underlying SWT widget's setSelection by index or text, using SWTBot's standard pattern of UIThreadRunnable.syncExec for thread-safe execution. SWTBot repositories have moved from git.eclipse.org cgit to GitHub (as noted in multiple sources). To view the exact implementation: 1. Navigate to https://github.com/eclipse-swtbot/org.eclipse.swtbot 2. Search for SWTBotCombo.java in the swt.finder module (likely examples/swtbot-eclipse/ or plugins/org.eclipse.swtbot.swt.finder/src/org/eclipse/swtbot/swt/finder/widgets/) 3. Look for public void setSelection(int index) or setSelection(String text) methods. Related evidence from Eclipse git mirrors: - Tests use bot.comboBox.setText("kikoo") for Combo interaction (source 1 from first search). - SWTBot generator rules include ComboSelectionRule and CComboSelectionRule, indicating SWTBotCombo likely handles both Combo and CCombo selection (source 9 from second search). - SWT CCombo has setSelection(Point selection) on its text field (source 2). No exact code snippet found in search highlights, but the GitHub repo is the authoritative up-to-date source per Eclipse migration notices (sources 6,7 across searches).

Citations:


Narrow the caught exception type to avoid masking real widget failures.

The broad catch (Exception ignored) can hide unrelated SWTBot issues (e.g., widget not found, dialog didn't appear as expected, combo label changed) that occur in setSelection(portPrefixOrExact). When such failures occur, the exception is swallowed and the fallback prefix-scan runs instead, risking selection of the wrong port because startsWith may match a different board's port. The original failure never gets reported.

Consider either:

  • Doing the prefix lookup first (deterministic and cheaper) and only falling back to exact setSelection if no prefix matches, or
  • Narrowing the caught type to exceptions actually thrown by SWTBot when an item is absent (e.g., IllegalArgumentException or similar), and at minimum logging the swallowed exception so CI output captures what triggered the fallback.

TestWidgetWaitUtility.waitForOperationsInProgressToFinishSync(bot);
shell.setFocus();
bot.button("Finish").click();
}

private static void whenTurnOffOpenSerialMonitorAfterFlashingInLaunchConfig() throws Exception
{
private static void whenTurnOffOpenSerialMonitorAfterFlashingInLaunchConfig() throws Exception {
LaunchBarConfigSelector configSelector = new LaunchBarConfigSelector(bot);
configSelector.clickEdit();
TestWidgetWaitUtility.waitForDialogToAppear(bot, "Edit Configuration", 20000);
bot.cTabItem("Main").show();
bot.cTabItem("Main").setFocus();
SWTBotCheckBox checkBox = bot.checkBox("Open Serial Monitor After Flashing");
if (checkBox.isChecked())
{
if (checkBox.isChecked()) {
checkBox.click();
}
bot.button("OK").click();
}

private static void whenFlashProject() throws IOException
{
private static void whenFlashProject() throws IOException {
ProjectTestOperations.launchCommandUsingContextMenu(projectName, bot, "Run Configurations...");
TestWidgetWaitUtility.waitForDialogToAppear(bot, "Run Configurations", 10000);
bot.tree().getTreeItem("ESP-IDF Application").select();
Expand All @@ -148,16 +203,24 @@ private static void whenFlashProject() throws IOException
bot.button("Run").click();
}

private static void thenVerifyFlashDoneSuccessfully() throws Exception
{
private static void thenVerifyFlashDoneSuccessfully() throws Exception {
ProjectTestOperations.waitForProjectFlash(bot);
}

static void cleanupEnvironment()
{
static void cleanupEnvironment() {
TestWidgetWaitUtility.waitForOperationsInProgressToFinishAsync(bot);
ProjectTestOperations.closeAllProjects(bot);
ProjectTestOperations.deleteAllProjects(bot);
}

private static class TargetPort {
final String target;
final String port;

TargetPort(String target, String port) {
this.target = target;
this.port = port;
}
}
}
}
}
Loading
Loading