Issue with finding match:
I've got a dialog with 4 radio buttons, which have these verbose labels:
- Web Page HTML file (for sharing, and printing)
- Comma Separated Values file (CSV, best for portable devices)
- Rich Text Format file (RTF, best for integration with your documents)
- Web Page HTML file (for sharing, and printing)
The following failed:
Ultimately, I had to fully spell it out. Given the verbosity of the radio button, this was pretty painful :)
Here's the thing: since "CSV" appears in the text of only one control, shouldn't it have matched? Although only a small percentage of the characters match on my target radio button, it's zero percent on all the others.
------ Full dialog dump:
MatchError: Could not find 'CommaSeparatedValuesFile' in '['', u'Web Page HTML file (for sharing, and printing)RadioButton', u'Collection Export WizardStatic', u'The collection export wizard will guide you through the process of exporting the collection file to a third-party file format for the convenience of printing and exchanging with others.UpDown', u'HelpButton', u'&Next >', 'ComboBox', u'Rich Text Format file (RTF, best for integration with your documents)RadioButton', u'Rich Text Format file (RTF, best for integration with your documents)', u'Collection Export Wizard', 'UpDown', u'The collection export wizard will guide you through the process of exporting the collection file to a third-party file format for the convenience of printing and exchanging with others.', u'CancelButton', u'With the following file organization:', u'&Next >Button', u'With the following file organization:ComboBox', u'With the following file organization:Static', u'#32770', 'Button', 'RadioButton3', u'The collection export wizard will guide you through the process of exporting the collection file to a third-party file format for the convenience of printing and exchanging with others.Static', 'Button3', u'I want to export my collection to:', u'Web Page HTML file (for sharing, and printing)', 'Button0', 'Button1', u'Plain text fileRadioButton', u'Help', 'Static1', 'Static0', 'Static3', 'Static2', 'Static5', 'Static4', 'Button2', 'RadioButton', u'I want to export my collection to:Static', u'Comma Separated Values file (CSV, best for portable devices)RadioButton', u'Plain text file', 'RadioButton4', 'Static', 'RadioButton2', 'RadioButton1', 'RadioButton0', u'Cancel', u'Collection Export Wizard#32770', u'Comma Separated Values file (CSV, best for portable devices)']'
Mark Mc Mahon
It makes sense that it should - but as you found out it doesn't.
In the latest release I changed the algorithm to make it more flexible - but also make it a little more strict.
I use difflib in the Python Standard library to find those texts that are a close match. And that returns a float between 1 and 0 giving how well 2 bits of text match (what you typed in and what the control text is). If a match is below a certain number it is not counted as a match. and the problem here is that the text of the control is long so CSV only matches 3 characters out of the lot..
ctrl = "Comma Separated Values file (CSV, best for portable devices)"
attr = "CSV"
matcher = difflib.SequenceMatcher()
And I cut off somewhere in the range of .6!
The only reason that I would be wary of adding a check for the text being in only one control is that things like
# do something with it
is too likely to succeed!
Just as I wrote the above what I could do is:
- Add a flag to my matching method that says whether to do this 'In' searching if all else fails.
- This flag would be set for normal attribute resolution
- this flag would be unset for Exists
But the only problem then is that Exists, Enabled, etc, will behave differently and it could set up some very hard to find bugs
while not app.dlg.CSV.IsEnabled():
# wait for the control to become enabled after clicking
this would not work :-(
I have been thinking at various times that it would be nicer to be able to specify title=, title_re = etc.
maybe something like
This will definitely need some comments before I go for it.
for now you can do what you pointed out or maybe
app.dlg.window_(title_re = ".*CSV.*").Click()
but that isn't much less typing is it :-(
Thanks for the insight.
I agree with your thinking. There's a fine line between accomodating the user with fuzzy logic, and doing the wrong thing if the app changes in the future and the wrong match is made.
In this case, the RE is perfect, I just didn't know about it. I'd suggest keeping it pythonic and not having a zillion ways to do things.. but making the congnitive leap from the nice app.dlg.ButtonName, to window_() calls, is kinda tough for us newbies.
>> while not app.dlg.CSV.IsEnabled():
This could be an infinite loop. I just came across this in the Export I've been working on. I used Time.sleep() Seems like specifying max wait time on a specific basis is frequently useful, something like..
Yeah - I was trying to make it easy - but not make the more complicated things impossible. There may be times when you need to take complete control over which control to select. (though this time you only need to help pywinauto a little)
> >> while not app.dlg.CSV.IsEnabled():
> This could be an infinite loop. I just came across this in the Export I've been working on. I used Time.sleep() Seems like specifying max wait time on a specific basis is frequently useful, something like..
Maybe this is a wart - but not necessarily an easy one to work around.
IsEnabled is just supposed to Retrun True or false (it is just a simple wrapper around the Windows API function IsEnabled - and as such I prefer to keep the name similar to windows). And I prefer to keep the method very simple
On other hand you may have been able to use:
which would have returned as soon as the control was not enabled and returns as soon as the control is no longer enabled (though this function looks somewhat badly designed at the moment - because there is no way of telling if the function returned because the control was disabled or because the function timed out.
This method is not implemented in any control but in the class that resolves controls (class pywinauto.application.WindowSpecification). This class is what is used to build up app.dlg.control, if you do app.dlg.Something or app.dlg.ctrl.Something it is this class that figures out if:
- Something is a member of WindowSpecification (python handles this resolution and doesn't call __getattr__)
- Something is a member of the Dialog class - then it returns the dlg.Something
- in app.dlg.ctrl.Something - as I currently only allow 2 levels of windows (Dialog/Control) the Something of ctrl will be returned.
but because of the first case above, methods of this class look like methods of Dialog or control.
Currently the following functions are defined.
WaitReady() # wait until window exists, is both visible and enabled, return ctrl when found, return None if timed out
Exists() # return true if the control exists (even if it is hidden or disabled)
WaitNotEnabled() # return when the control is no longer enabled (doesn't return a value)
WaitNotVisible() # return when the control is no longer visible (doesn't return a value)
each of these has both a default timeout and a wait_interval that can be overridden.
But I guess I should complete the list of possibilties...?
I was thinking that these could maybe be free standing functions..
application.WaitVisible(app.dlg.csv) - but unfortunately app.dlg.csv would be resolved first and you would get an exception saying that the control can't be found (if the control is not visible) before the function would run!
if they were methods of the Application class then you might be able to have something like:
app.WaitVisible("dlg.csv") - but then how do I know if that is dlg.csv, or a dialog with title like 'dlg.csv'!
So for now I think I should try to make it clear in teh documentation that methods of Controls try to be as simple as possible (I try to avoid timeouts as much as possible). And also highlight that the WaitXXX methods exist (and finish off implementing them.)