Using Win32::GuiTest to get data from grid display control (AfxWnd42s) - perl

I'm currently writing some perl scripts to test one of our GUI applications. I'm using perl's Win32::GuiTest as a foundation, and it's pretty functional for the most part. However, I am now stuck trying to get data out of a grid control (not sure what the actual name of the widget is since this is supposed to be black-box testing). Using WinSpy++, I can see that it's class is "AfxWnd42s", whatever that means. I can get the window handle with this code:
#
# check that listed ports match the spec for this model
#
my #portgrids = FindWindowLike($win,undef,"AfxWnd42s");
if(not #portgrids){ die "can't find port grid"; }
for(#portgrids){
my $styles = GetWindowLong($_,Win32::GuiTest::GWL_STYLE());
my $text = WMGetText($_);
$wintext = GetWindowText($_);
printf("static w=%x, s=$wintext, style=%x text=<<$text>>\n",$_,$styles);
}
Neither text, nor wintext contain any value, even though there is data in the grid control. I've tried a number of different options to GetWindowLong, but nothing seems to return anything about the contents of the grid. Also, The windows I'm scanning have no children. I'm stuck.
Unfortunately I don't have access to the code or the dev environment to build the application. I can only go by what WinSpy++ tells me about the window components.

Related

Shell.Application & errors

To clarify, I am specifically asking about how to HANDLE errors when using Shell.Application. Or more to the point, CAN one handle errors, and IF SO, how. The font example is just that, an example of the current situation I am trying to solve. But the question remains, can one handle (not avoid, handle) an error when using Shell.Application? There may well be some subtlety to the answer, I.e in general you can, but specifically not in the fonts example. That seems to be the case, and I am inclined to decide that Shell.Application is old, broken technology that should simply not be used, because it can't be robust in all use cases, which to me says it's potentially fragile in any use case.
I am attempting to refine my use of Shell.Application.CopyHere(), specifically for use installing fonts. What I hope to do is address the occasional error where a font file is corrupt or otherwise not a valid font file.
So far as I can tell from this there really is no way to address this. I can use argument value 16 Respond with "Yes to All" for any dialog box that is displayed. to perhaps get past the error, but with no return code I have no way to log a resulting error. Using .CopyHere() in PowerShell with a Try/Catch doesn't work either. Is this just old technology from a time when Microsoft just accepted things failing ungracefully? Or am I missing a technique that solves the issue?
EDIT: Based on that link I provided, I tried the 1024 argument, Do not display a user interface if an error occurs. Like so
$fontFolder.CopyHere($fontFilePath, 1024)
Doesn't seem to do what it says it does, since I am seeing a dialog that says
Cannot install bogus.ttf
The file ... does not appear to be a valid font.
So, not only not able to get a meaningful error back, but the presence of an error disrupts execution of the script and requires user interaction. Ugh.
EDIT 2:
Not really a minimal code example, since my question is CAN this be done, even before how. But this is what I just tried.
fontFilePath = '\\px\Rollouts\Misc\Fonts\bogus.ttf'
$fontFolderPath = "$env:windir\Fonts"
$fontFolder = $(New-Object -ComObject:Shell.Application).Namespace($fontFolderPath)
$fontFolder.CopyHere($fontFilePath, 1024)
Based on what that 1024 argument claims to do, I would expect this to fail, but also not stop processing while a dialog waits for user interaction.
Also, worth noting that bogus.ttf is simply an empty text file renamed with TTF extension. So, guaranteed not to successfully install a font.
It seems that the Fonts folder, while it does implement Folder.CopyHere, does not evaluate the flags passed in the second parameter. Doesn't seem there are any other standard utilities to install fonts programmatically, or verify the integrity of the TTF non-interactively.
So this leaves us the option of rolling our own Font registration using the Win32 API. Basically, you have to copy the font to the Fonts folder:
# For .NET versions earlier than 4.0, hardcode to C:\Windows\Fonts
# Fonts special folder is new in 4.0
$fontDir = [System.Environment]::GetFolderPath([System.Environment.SpecialFolder]::Fonts)
Copy-Item C:\Path\To\Font.ttf "${fontDir}\Font.ttf"
This next call isn't strictly necessary, as it only adds the font temporarily to your user session, but it does function as an integrity check to see if the font is valid and can be imported. You need to P/Invoke AddFontResource from the Win32 API:
Add-Type -Name Gdi32 -Namespace Win32 -MemberDefinition #"
[System.Runtime.InteropServices.DllImport("gdi32.dll")]
public static extern int AddFontResource(string lpszFilename);
"#
# Returns 1 on success or 0 on failure, if you want your error checking here
[Win32.Gdi32]::AddFontResource("${fontDir}\FontFileName.ttf")
And then register it at the following registry key. This piece is necessary for persisting the font you copied to $fontDir:
New-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts' -Name "FontName" -Value "FontFileName.ttf"
You can programmatically get the FontName using the PrivateFontCollection class, and the Families[x].Name property under that, where x is the index of the font in the collection you want the name for.
To address your edit, there isn't going to be a "one size fits all" approach when it comes to handling errors in Shell.Application. There are multiple reasons to avoid using Shell.Application, such as:
APIs are wrought with legacy behavior, such as using modal dialogs to report errors
Windows standards are implemented inconsistently, such as Fonts ignoring the documented CopyFile flags
Not available in a non-interactive session
The first two points are cases the example in your question hits. The third comes into play in many automation scenarios, especially when a non-interactive service accounts are used to execute commands. There are very few cases where something can only be done via Shell.Application, and as such it's almost always best to avoid it if there is an alternative API. Your font installation scenario is an excellent example of this.

How to debug long scripts in Chrome?

I have a page which has a script tag. The script inside that tag is very long, but I would like to debug it. Unfortunately, I cannot scroll to the relevant place in the Console, because after a certain length the script is simply not displayed, see the attachment:
As you can see, it ends with
return !filt...
The actual function looks like this:
this.validate = function(filters) {
for (var filter in filters) {
if (!innerValidation(filters[filter].filterType, filters[filter].evaluatedValue, data[filters[filter].key])) {
return filters[filter].isOr;
}
}
return !filters[filter].isOr;
};
Question: Why does Chrome truncate my script and how could that be changed?
Note, that I know I could load it from an external file, but I am actually interested to know the cause of this behavior.
The display of the script is truncated but it still parses and runs the code correctly. You should be able to view the full code in the Sources tab under the relevant host and put breakpoints in there.
If you have a long script, it is best to make that into its own file instead. This will provide you with the best debugging experience.
The Elements panel truncates large scripts to help keep things fast and there is no way to undo this. Therefore, you would need to use some external debugging tools to try and get at this, but even then most of them work best with external script files as well.
Inline scripts should be very short if ever used. For any decent sized chunks of scripting, allocate that into its own file.

What is the full command for gdal_calc in ipython?

I've been trying to use raster calculation in ipython for a tif file I have uploaded, but I'm unable to find the whole code for the function. I keep finding examples such as below, but am unsure how to use this.
gdal_calc.py -A input.tif --outfile=result.tif --calc="A*(A>0)" --NoDataValue=0
I then tried another process by assigning sections, however this still doesn't work (code below)
a = '/iPythonData/cstone/prec_7.tif'
outfile = '/iPythonData/cstone/prec_result.tif'
expr = 'A<125'
gdal_calc.py -A=a --outfile=outfile --calc='expr' --NoDataValue=0
It keeps coming up with can't assign to operator. Can someone please help with the whole code.
Looking at the source code for gdal_calc.py, the file is only about 300 lines. Here is a link to that file.
https://raw.githubusercontent.com/OSGeo/gdal/trunk/gdal/swig/python/scripts/gdal_calc.py
The punchline is that they just create an OptionParser object in main and pass it to the doit() method (Line 63). You could generate the same OptionParser instance based on the same arguments you pass to it via the command-line and call their doit method directly.
That said, a system call is perfectly valid per #thomas-k. This is only if you really want to stay in the Python environment.

Selenium WebDriver with Perl

I am trying to run the Selenium driver with Perl bindings, and due to the lack of examples and documentation, I am running into some roadblocks. I have figured out how to do some basic things, but I seem to be running into some issues with other simple things like validating the text on a page using Remote::Driver package.
If I try to do something like this:
$sel->get("https://www.yahoo.com/" );
$ret = $sel->find_element("//div[contains( text(),'Thursday, April 26, 2012')]");
I get a message back that the element couldn't be found. I am using xpath because the driver package doesn't appear to have a sub specific for finding text.. at least not that I've found.
If my xpath setup is wrong or if someone knows a better way, that would be extremely helpful. I'm having problems with some button clicking too.. but this seems like it should be easier and is bugging me.
Finding text on a web page and comparing that text to some "known good value" using Selenium::Remote::Driver can be implemented as follows:
File: SomeWebApp.pm
package SomeWebApp;
sub get_text_present {
my $self = shift;
my $target = shift;
my $locator = shift;
my $text = $self->{driver}->find_element($target, $locator)->get_text();
return $text;
}
Somewhere in your test script: test.pl
my $text = $some_web_app->get_text_present("MainContent_RequiredFieldValidator6", "id");
The above finds the element identified by $target using the locating scheme identified by $locator and stores it in the variable $text. You can then use that to compare / validate as required / needed.
https is a tad slower loading than http. Although WebDriver is pretty good about waiting until it's figured out that the requested page is fully loaded, maybe you need to give it a little help here. Add a sleep(2); after the get() call and see if it works. If it does, try cutting down to 1 second. You can also do a get_title call to see if you've loaded the page you think you have.
The other possibility is that your text target isn't quite exactly the same as what's on the page. You could try looking first for one word, such as "April", and see if you get a hit, and then expand until you find the mismatch (e.g., does this string actually have a newline or break within it? How about an HTML entity such as a non-breaking space?). Also, you are looking for that bit of text anywhere under a div (all child text supposedly is concatenated, and then the search done). That would more likely cast too wide a net than not get anything at all, but it's worth knowing.

How can I run a CGI::Application run mode from the command line?

I have a run mode in my CGI::Application web-app that I would like to be able to trigger from the command line so i can automate it. From the web-app's perspective it does some processing then sends the results in an email.
When called from the web interface it passes in a set of parameters (email address, which query to run, date, etc) so these need to be passed in.
How can I construct a call to the CGI::Application app that will be the same as if I ran it from the web?
The original CGI specification makes it easy to run things from the command line and was fully intended not as a specific HTTP-only interface but something that could handle FTP and gopher as well as new top-level URL schemes. I know what I wanted when I helped specify it.
The spec I referenced should give you all you need, but for the most part it is just a collection of environment variables. If you see a request for:
http://some.server.com/some/path?a=b&c=d
The environment variables come out looking like this:
SERVER_PROTOCOL=http
REQUEST_METHOD=GET
HTTP_HOST=some.server.com
SERVER_PORT=80
PATH_INFO=/some/path
QUERY_INFO=a=b&c=d
To reverse the polarity of that in Perl would go something like this:
$ENV{'SERVER_PROTOCOL'} = 'http';
$ENV{'REQUEST_METHOD'} = 'GET';
$ENV{'SERVER_PORT'} = 80;
$ENV{'PATH_INFO'} = '/some/path';
$ENV{'QUERY_INFO'} = 'a=b&c=d';
system("perl your-CGI-script.pl");
Things get a bit more complicated in handling POST queries and there are more possible environment variables that may be required. Worst case you can enumerate them all with a quick CGI script something like:
print "Content-Type: text/plain\r\n\r\n";
foreach (keys(%ENV))
{
print "$_=$ENV{$_}\r\n";
}
Now put that on the web server in place of your CGI script and you'll see all the environment that gets passed in (and the original environment so you'll need to make a few judgement calls).
Upon further digging through the CGI::App and the CGI documentation, it appeared to be more straightforward than I thought. The simplest case (no real argument handling or dealing with the output from the webapp run call) is:
#!/usr/bin/perl
use strict;
use warnings;
use CGI;
use WebApp;
my $cgi = new CGI( \%{#ARGV} );
my $webapp = WebApp->new( QUERY => $cgi );
$webapp->run();
It just takes a series of space separated name value pairs to create the CGI. You need to pass in the run mode and all the arguments.
I'm the maintainer of CGI::Application, and I do this all the time-- I have dozen of cron scripts built with CGI::Application because it's convenient to share the infrastructure with the application.
The simplest approach is this:
# There is no browser to return results to.
$ENV{CGI_APP_RETURN_ONLY} = 1;
my $app = WebApp->new;
$app->direct_run_mode_method;
In that example, you bypass the normal flow and call a method directly. Be sure you don't need any of the "setup" or "teardown" actions to happen in that case.
If you just have one run mode you are calling, you can also just set the "start_mode", and call run(), so then the default run mode is called by default.
Another idea: you can use a module like Getopt::Long and pass in values through the PARAM hash to new(), or completely replace the run-mode selection process. Here's an example where command line flags are used to determine the run mode:
sub setup {
my $self = shift;
$self->start_mode('send_error_digests');
$self->run_modes([qw/
send_error_digests
help
/]);
my ($dry_run, $help);
GetOptions(
'dry-run' => \$dry_run,
'help' => \$help
);
$self->param('dry_run' => $dry_run);
$self->mode_param(sub {
return 'help' if $help;
return $self->start_mode();
});
}
Thusly:
$ perl yourscript.pl field1=value1 field2=value2
Perl's CGI library takes care of the magic for you, and it appears that CGI::Application relies on CGI (judging from their example code).
Instead of having to go through CGI::Application every time you want to get something done, enforce a proper separation of concerns, perhaps using an MVC setup. All of the functionality should exist outside of the CGI::Application stuff since that should only work as a controller. Once you separate out those bits, you can easily write other controllers for other input methods.
Don't write a web application; write an an application that happens to have a web interface. When you have that, you can easily give your application other sorts of interfaces.
You could automate by calling the web app using curl, wget, or an LWP GET-script with the appropriate parameters. I've used a similar system for cron-driven tasks with a Catalyst application.
That deals with all the environment variables for you..