Automating a Delphi 2007 application with a Python script - how do I unambiguously reference controls?

166 Views Asked by At

I have a legacy Delphi application that I need to automate for testing purposes. I'm having difficulty with directing the automation requests to the correct component. The included example demonstrates the issue (on my environment anyway). Heres a simple Delphi application I'm wanting to control:

Project1.dpr

program Project1;

uses
  Forms,
  Unit1 in 'Unit1.pas' {Form1};

{$R *.res}

begin
  Application.Initialize;
  Application.MainFormOnTaskbar := True;
  Application.CreateForm(TForm1, Form1);
  Application.Run;
end.

Unit1.pas

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls;

type
  TForm1 = class(TForm)
    Memo1: TMemo;
    Button1: TButton;
    CheckBox1: TCheckBox;
    Timer1: TTimer;
    Button2: TButton;
    Button3: TButton;
    Button4: TButton;
    Button5: TButton;
    Label2: TLabel;
    Panel1: TPanel;
    procedure ControlClick(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.ControlClick(Sender: TObject);
begin
Memo1.Lines.Add (Format ('Control "%s" clicked', [TControl (Sender).Name])) ;
end;


procedure TForm1.Timer1Timer(Sender: TObject);
begin
if Assigned(Screen.ActiveControl) then
    begin
    Panel1.Caption := Screen.ActiveControl.Name ;
    end
else
    begin
    Panel1.Caption := 'None.' ;
    end ;
end ;

end.

Unit1.dfm

object Form1: TForm1
  Left = 0
  Top = 0
  Caption = 'Form1'
  ClientHeight = 212
  ClientWidth = 407
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'Tahoma'
  Font.Style = []
  OldCreateOrder = False
  PixelsPerInch = 96
  TextHeight = 13
  object Label2: TLabel
    Left = 73
    Top = 193
    Width = 32
    Height = 13
    Caption = 'Focus:'
  end
  object Memo1: TMemo
    Left = 24
    Top = 8
    Width = 201
    Height = 177
    TabOrder = 0
  end
  object Button1: TButton
    Left = 304
    Top = 11
    Width = 75
    Height = 25
    Caption = '1'
    TabOrder = 1
    OnClick = ControlClick
  end
  object CheckBox1: TCheckBox
    Left = 296
    Top = 171
    Width = 97
    Height = 17
    Caption = 'CheckBox1'
    TabOrder = 6
    OnClick = ControlClick
  end
  object Button2: TButton
    Left = 304
    Top = 42
    Width = 75
    Height = 25
    Caption = '2'
    TabOrder = 2
    OnClick = ControlClick
  end
  object Button3: TButton
    Left = 304
    Top = 73
    Width = 75
    Height = 25
    Caption = '3'
    TabOrder = 3
    OnClick = ControlClick
  end
  object Button4: TButton
    Left = 304
    Top = 104
    Width = 75
    Height = 25
    Caption = '4'
    TabOrder = 4
    OnClick = ControlClick
  end
  object Button5: TButton
    Left = 304
    Top = 135
    Width = 75
    Height = 25
    Caption = '5'
    TabOrder = 5
    OnClick = ControlClick
  end
  object Panel1: TPanel
    Left = 116
    Top = 191
    Width = 109
    Height = 17
    TabOrder = 7
  end
  object Timer1: TTimer
    Interval = 300
    OnTimer = Timer1Timer
    Left = 240
    Top = 152
  end
end

Automate.py

import pywinauto
from pywinauto import application

def Click (AForm, AControlName):
    FControl = AForm [AControlName]
    FControl.click()
    return ()

FormTitle = "Form1"
try:
    app = application.Application().connect(title=FormTitle)
    form1 = app[FormTitle]

    Click (form1, "Button1")
    Click (form1, "Button2")
    Click (form1, "Button3")
    Click (form1, "Button4")
    Click (form1, "Button5")

    Click (form1, "Checkbox1")

    exit (0)

except pywinauto.application.ProcessNotFoundError:
    print(f"No running instance of {FormTitle} found.")
    exit (1)

except Exception as e:
    print(f"An error occurred: {str(e)}")
    exit (1)

All is good, except that the buttons are reversed. If I direct a click to Button1, Button5 registers a click. Other experiments have shown that it's not necessarily predictable as to how it is going to redirect the request - the checkbox is OK for example, though I never tried a larger number of those. All I want is an unambiguous way of referencing a control - because the control name doesn't cut it.

UPDATE Further experiments have seemed to indicate that the control's caption is what I need to pass to the call to send a click message to the control.

1

There are 1 best solutions below

0
Vasily Ryabov On

Access by attribute or by item performs a typo resistant search procedure. The indexing in this procedure is different. This is described in more details in the Getting Started Guide.

For search by exact caption you need to make search criteria this way (it works faster!):

form1.child_window(title="1", class_name="TButton").click()
form1.child_window(title="2", class_name="TButton").click()    
form1.child_window(title="3", class_name="TButton").click()

If you want to know the correct specification, just dump it:

form1.dump_tree()

So you can copy-paste such child_window specifications from this dump.

BTW, there is a known issue with connect method that requires explicit timeout for now: connect(title=FormTitle, timeout=10)