/*
* MIT License
*
* Copyright (c) Microsoft Corporation.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.Runtime.Serialization;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
#nullable enable
namespace Microsoft.Playwright
{
public class ElementHandleClickOptions
{
public ElementHandleClickOptions() { }
public ElementHandleClickOptions(ElementHandleClickOptions clone)
{
if (clone == null)
{
return;
}
Button = clone.Button;
ClickCount = clone.ClickCount;
Delay = clone.Delay;
Force = clone.Force;
Modifiers = clone.Modifiers;
NoWaitAfter = clone.NoWaitAfter;
Position = clone.Position;
Timeout = clone.Timeout;
Trial = clone.Trial;
}
/// <summary><para>Defaults to <c>left</c>.</para></summary>
[JsonPropertyName("button")]
public MouseButton? Button { get; set; }
/// <summary><para>defaults to 1. See <see cref="UIEvent.detail"/>.</para></summary>
[JsonPropertyName("clickCount")]
public int? ClickCount { get; set; }
/// <summary>
/// <para>
/// Time to wait between <c>mousedown</c> and <c>mouseup</c> in milliseconds. Defaults
/// to 0.
/// </para>
/// </summary>
[JsonPropertyName("delay")]
public float? Delay { get; set; }
/// <summary>
/// <para>
/// Whether to bypass the <a href="https://playwright.dev/dotnet/docs/actionability">actionability</a>
/// checks. Defaults to <c>false</c>.
/// </para>
/// </summary>
[JsonPropertyName("force")]
public bool? Force { get; set; }
/// <summary>
/// <para>
/// Modifier keys to press. Ensures that only these modifiers are pressed during the
/// operation, and then restores current modifiers back. If not specified, currently
/// pressed modifiers are used.
/// </para>
/// </summary>
[JsonPropertyName("modifiers")]
public IEnumerable<KeyboardModifier>? Modifiers { get; set; }
/// <summary>
/// <para>
/// Actions that initiate navigations are waiting for these navigations to happen and
/// for pages to start loading. You can opt out of waiting via setting this flag. You
/// would only need this option in the exceptional cases such as navigating to inaccessible
/// pages. Defaults to <c>false</c>.
/// </para>
/// </summary>
[JsonPropertyName("noWaitAfter")]
public bool? NoWaitAfter { get; set; }
/// <summary>
/// <para>
/// A point to use relative to the top-left corner of element padding box. If not specified,
/// uses some visible point of the element.
/// </para>
/// </summary>
[JsonPropertyName("position")]
public Position? Position { get; set; }
/// <summary>
/// <para>
/// Maximum time in milliseconds, defaults to 30 seconds, pass <c>0</c> to disable timeout.
/// The default value can be changed by using the <see cref="IBrowserContext.SetDefaultTimeout"/>
/// or <see cref="IPage.SetDefaultTimeout"/> methods.
/// </para>
/// </summary>
[JsonPropertyName("timeout")]
public float? Timeout { get; set; }
/// <summary>
/// <para>
/// When set, this method only performs the <a href="https://playwright.dev/dotnet/docs/actionability">actionability</a>
/// checks and skips the action. Defaults to <c>false</c>. Useful to wait until the
/// element is ready for the action without performing it.
/// </para>
/// </summary>
[JsonPropertyName("trial")]
public bool? Trial { get; set; }
}
}
#nullable disable
using Microsoft.Playwright;
using System.Threading.Tasks;
namespace QualysPlaywright
{
public class QualysWrapperPW
{
private readonly IPage _page;
public QualysWrapperPW(IBrowser browser)
{
_page = browser.NewPageAsync().Result;
}
public async Task OpenExtension()
{
await _page.GotoAsync("chrome-extension://abnnemjpaacaimkkepphpkaiomnafldi/panel/index.html");
}
public async Task StartCapture(string testCaseName)
{
_page.Dialog += async (object sender, IDialog dialog) => await dialog.AcceptAsync(testCaseName);
var untitledTestCaseElement = await _page.QuerySelectorAsync("//*[@id='testCase-grid']//*[text()='Untitled Test Case']");
await untitledTestCaseElement.ClickAsync(new ElementHandleClickOptions { Button = Microsoft.Playwright.MouseButton.Right });
await _page.ClickAsync("//a[text()='Rename Test Case']");
await ClickRecord();
}
public async Task AddWait()
{
// you could provide some logic to insert waiting between steps statically,
// or alternatively use driver events to insert these commands or overwrite the edited ones instead to make the script less brittle
// obviously if the tests have to take into account that the driver could have qualys attached or not is not a good idea
// so it would be up to you to figure it out,
// if you have many loaders you could just use a post click event to insert waiting for the loader to appear and disappear, same with navigation
}
public async Task StopCapture(string fileName)
{
await ClickRecord();
await _page.ClickAsync("#download");
_page.Dialog += async (object sender, IDialog dialog) => await dialog.AcceptAsync(fileName);
}
private async Task ClickRecord() =>
await _page.ClickAsync("#record");
}
}
using Microsoft.Playwright;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace train12306
{
public class Train
{
private readonly string _fromStation = ConfigurationManager.AppSettings["fromStation"];
private readonly string _toStation = ConfigurationManager.AppSettings["toStation"];
private readonly string _date = ConfigurationManager.AppSettings["date"];
private readonly string[] _trainNos;
private readonly string[] _passengerNames;
private readonly (int, string)[] _seatTypes;
private readonly int _refreshTimes = int.Parse(ConfigurationManager.AppSettings["refreshTimes"]);
private readonly bool _headless = bool.Parse(ConfigurationManager.AppSettings["Headless"]);
private IPage _page;
// 座ä½tdåå
æ ¼èµ·å§ä½ç½®
private const int _startTdNumber = 2;
private readonly Dictionary<string, (int, string)> _seatDict = new Dictionary<string, (int, string)>
{
{"business_seat",(_startTdNumber,"9") },
{"first_class_seat",(_startTdNumber+1,"M") },
{"second_class_seat",(_startTdNumber+2,"O") },
{"senior_soft_sleeper",(_startTdNumber+3,"6") },
{"soft_sleeper",(_startTdNumber+4,"4") },
{"move_sleeper",(_startTdNumber+5,"F") },
{"hard_sleeper",(_startTdNumber+6,"3") },
{"soft_seat",(_startTdNumber+7,"2") },
{"hard_seat",(_startTdNumber+8,"1") },
{"no_seat",(_startTdNumber+9,"1") }
};
private const char _separator = ',';
public Train()
{
_trainNos = ConfigurationManager.AppSettings["trainNo"].Split(_separator);
_passengerNames = ConfigurationManager.AppSettings["passengerName"].Split(_separator);
var seatTypes = ConfigurationManager.AppSettings["seatType"].Split(_separator);
_seatTypes = new (int, string)[seatTypes.Length];
for (int i = 0; i < seatTypes.Length; i++)
{
_seatTypes[i] = _seatDict[seatTypes[i]];
}
}
/// <summary>
/// å¼å§
/// </summary>
/// <returns></returns>
public async Task StartAsync()
{
try
{
Console.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss} ----> åå§åæµè§å¨ã");
using var playwright = await Playwright.CreateAsync();
await using var browser = await playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions
{
Headless = _headless
});
// 设置é»è®¤çªå£å¤§å°
var context = await browser.NewContextAsync(new BrowserNewContextOptions
{
ViewportSize = new ViewportSize() { Width = 1920, Height = 1080 }
});
_page = await context.NewPageAsync();
await LoginAsync();
}
catch (Exception ex)
{
Console.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss} ----> ${ex.Message}");
}
}
/// <summary>
/// ç»å½
/// </summary>
/// <returns></returns>
private async Task LoginAsync()
{
Console.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss} ----> æå¼ç»å½é¡µé¢ï¼çå¾
æ«ç ã");
await _page.GotoAsync("https://kyfw.12306.cn/otn/resources/login.html");
// çå¾
ç¨æ·ç»å½
await _page.WaitForURLAsync("**/otn/view/index.html", new PageWaitForURLOptions
{
Timeout = 60 * 1000
});
Console.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss} ----> æ«ç ç»å½æåã");
// ç¨æ·ç»éåç´æ¥è·³è½¬ä¹°ç¥¨çé¢
await _page.GotoAsync("https://kyfw.12306.cn/otn/leftTicket/init?linktypeid=dc");
await SetTicketFormAsync();
}
/// <summary>
/// 设置买票ç表å
/// </summary>
/// <returns></returns>
private async Task SetTicketFormAsync()
{
Console.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss} ----> å¼å§è®¾ç½®æ¢ç¥¨è¡¨åã");
// å
³éå¼¹çª
await _page.ClickAsync("#gb_closeDefaultWarningWindowDialog_id");
// 1ãæ¸
é¤åºåç«è¾å
¥æ¡ï¼è®¾ç½®ç¦ç¹
var fromStationDom = await _page.QuerySelectorAsync("#fromStationText");
await fromStationDom.FillAsync("");
await fromStationDom.HoverAsync();
await fromStationDom.FocusAsync();
// è¾å
¥åºåç«
await _page.Keyboard.TypeAsync(_fromStation, new KeyboardTypeOptions { Delay = 100 });
await _page.ClickAsync($"#panel_cities span.ralign:text-is('{_fromStation}')");
// 2ãæ¸
é¤ç®çå°è¾å
¥æ¡ï¼è®¾ç½®ç¦ç¹
var toStationDom = await _page.QuerySelectorAsync("#toStationText");
await toStationDom.FillAsync("");
await toStationDom.HoverAsync();
await toStationDom.FocusAsync();
// è¾å
¥ç®çå°
await _page.Keyboard.TypeAsync(_toStation, new KeyboardTypeOptions { Delay = 100 });
await _page.ClickAsync($"#panel_cities span.ralign:text-is('{_toStation}')");
// 3ãè¾å
¥æ¥æ
var trainDateDom = await _page.QuerySelectorAsync("#train_date");
await trainDateDom.FillAsync("");
await trainDateDom.HoverAsync();
await trainDateDom.FocusAsync();
// è¾å
¥ç®çå°
await _page.Keyboard.TypeAsync(_date, new KeyboardTypeOptions { Delay = 100 });
await _page.Keyboard.PressAsync("Enter");
await QueryTicketAsync();
}
/// <summary>
/// æ¥ç¥¨
/// </summary>
/// <returns></returns>
private async Task QueryTicketAsync()
{
Console.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss} ----> å¼å§æ¥è¯¢ä½ç¥¨ã");
await _page.ClickAsync("#query_ticket");
// çå¾
ajax请æ±
await _page.WaitForRequestFinishedAsync();
var queryLeftTrDoms = await _page.QuerySelectorAllAsync("#queryLeftTable tr:visible");
Console.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss} ----> ä¸å
±æç´¢å°{queryLeftTrDoms.Count}æ¡ä¿¡æ¯ã");
foreach (var tr in queryLeftTrDoms)
{
var trainNoDom = await tr.QuerySelectorAsync("td:nth-child(1) a.number");
var trainNo = await trainNoDom.InnerTextAsync();
if (_trainNos.Contains(trainNo))
{
// æ ¡éªåº§ä½ææ²¡æç¥¨
foreach (var seatType in _seatTypes)
{
// var test = await tr.QuerySelectorAsync("td:nth-child(2)");
var seatTypeDom = await tr.QuerySelectorAsync($"td:nth-child({seatType.Item1})");
var ticketText = await seatTypeDom.InnerTextAsync();
if (HasTicket(ticketText))
{
var result = await BuyTicketAsync(tr, seatType.Item2);
if (result) return;
}
}
}
}
Thread.Sleep(_refreshTimes);
await QueryTicketAsync();
}
private async Task<bool> BuyTicketAsync(IElementHandle tr, string seatType)
{
Console.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss} ----> å¼å§ä¹°ç¥¨ã");
var buyTicketDom = await tr.QuerySelectorAsync("a.btn72");
if (buyTicketDom != null)
{
await buyTicketDom.ClickAsync();
// çå¾
跳转å¾
确认çé¢
await _page.WaitForURLAsync("**/otn/confirmPassenger/initDc", new PageWaitForURLOptions
{
Timeout = 60 * 1000
});
Console.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss} ----> å è½½ä¹å®¢ä¿¡æ¯æåï¼ã");
// éæ©ä¹è½¦äºº
foreach (var passengerName in _passengerNames)
{
await _page.CheckAsync($"#normal_passenger_id label:text-is('{passengerName}')");
}
Console.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss} ----> éæ©ä¹å®¢æåï¼ã");
// éæ©åº§ä½ç±»å
var ticketInfoDoms = await _page.QuerySelectorAllAsync("#ticketInfo_id tr:visible");
int index = 1;
foreach (var ticketInfo in ticketInfoDoms)
{
var seatTypeDom = await ticketInfo.QuerySelectorAsync($"#seatType_{index}");
await seatTypeDom.SelectOptionAsync(seatType);
index++;
}
Console.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss} ----> éæ©åº§ä½æåï¼ã");
// æäº¤è®¢å
await _page.ClickAsync("#submitOrder_id");
Console.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss} ----> æäº¤è®¢åæåï¼ã");
// 确认订å
var submitDom = await _page.WaitForSelectorAsync("#qr_submit_id");
// å»¶è¿5såç¹
await submitDom.ClickAsync(new ElementHandleClickOptions { Delay = 5 * 1000 });
Console.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss} ----> 确认订åæåï¼ã");
// çå¾
åºç¥¨ãæ¯ä»
await _page.WaitForURLAsync("**/otn//payOrder/init", new PageWaitForURLOptions
{
Timeout = 60 * 1000
});
Console.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss} ----> åºç¥¨æåãæåä½ ï¼æ¢ç¥¨æåï¼è¯·å¨30åéç»å½12306æ¯ä»ãï¼ã");
return true;
}
else
return false;
}
private bool HasTicket(string ticketText)
{
if (string.IsNullOrEmpty(ticketText))
return false;
else if (ticketText == "æ")
return true;
else if (int.TryParse(ticketText, out int number))
return number > _passengerNames.Length;
else
return false;
}
}
}